diff --git a/api/docs/release.dox b/api/docs/release.dox
index 00c58fdd242629c4cae7f8de6ba36e063dc28255..1741b3a528d96c99d6b219ed9523304de9f005db 100644
--- a/api/docs/release.dox
+++ b/api/docs/release.dox
@@ -141,6 +141,10 @@ changes:
    member fields of classes are now following a consistent style with
    an underscore suffix.  References to renamed fields will need to be
    updated.
+ - A change in the load callbacks used with drmodtrack_add_custom_data()
+   and drmemtrace_custom_module_data(): they each take an additional parameter, the
+   segment index.  The custom data field is now per-segment and not per-module,
+   and all callbacks are invoked separately for each segment.
 
 The changes between version \DR_VERSION and 8.0.0 include the following minor
 compatibility changes:
diff --git a/clients/drcachesim/tests/burst_replace.cpp b/clients/drcachesim/tests/burst_replace.cpp
index a47cae88d3a087317b6f063f11241cf3fad3d048..cba8977a0c68334ee2377354a3e126b2ebe80f53 100644
--- a/clients/drcachesim/tests/burst_replace.cpp
+++ b/clients/drcachesim/tests/burst_replace.cpp
@@ -1,5 +1,5 @@
 /* **********************************************************
- * Copyright (c) 2016-2020 Google, Inc.  All rights reserved.
+ * Copyright (c) 2016-2021 Google, Inc.  All rights reserved.
  * **********************************************************/
 
 /*
@@ -139,8 +139,12 @@ local_create_dir(const char *dir)
 }
 
 static void *
-load_cb(module_data_t *module)
+load_cb(module_data_t *module, int seg_idx)
 {
+#ifndef WINDOWS
+    if (seg_idx > 0)
+        return (void *)module->segments[seg_idx].start;
+#endif
     return (void *)module->start;
 }
 
@@ -163,7 +167,7 @@ parse_cb(const char *src, OUT void **data)
 static std::string
 process_cb(drmodtrack_info_t *info, void *data, void *user_data)
 {
-    assert((app_pc)data == info->start || info->containing_index != info->index);
+    assert((app_pc)data == info->start);
     assert(user_data == MAGIC_VALUE);
     return "";
 }
diff --git a/clients/drcachesim/tracer/drmemtrace.h b/clients/drcachesim/tracer/drmemtrace.h
index 0ecb12e33c729817cfb93bfa16917934b4534f9c..bcb9bfbff8fa7aafc73df7519478fdeaf6717605 100644
--- a/clients/drcachesim/tracer/drmemtrace.h
+++ b/clients/drcachesim/tracer/drmemtrace.h
@@ -249,17 +249,19 @@ drmemtrace_get_funclist_path(OUT const char **path);
 DR_EXPORT
 /**
  * Adds custom data stored with each module in the module list produced for
- * offline trace post-processing.  The \p load_cb is called for each new module,
+ * offline trace post-processing.  The \p load_cb is called for each segment
+ * of each new module (with \p seg_idx indicating the segment number, starting at 0),
  * and its return value is the data that is stored.  That data is later printed
  * to a string with \p print_cb, which should return the number of characters
- * printed or -1 on error.  The data is freed with \p free_cb.
+ * printed or -1 on error.  The data is freed with \p free_cb.  Each is called
+ * separately for each segment of each module.
  *
  * On the post-processing side, the user should create a custom post-processor
  * by linking with raw2trace and calling raw2trace_t::handle_custom_data() to provide
  * parsing and processing routines for the custom data.
  */
 drmemtrace_status_t
-drmemtrace_custom_module_data(void *(*load_cb)(module_data_t *module),
+drmemtrace_custom_module_data(void *(*load_cb)(module_data_t *module, int seg_idx),
                               int (*print_cb)(void *data, char *dst, size_t max_len),
                               void (*free_cb)(void *data));
 
diff --git a/clients/drcachesim/tracer/instru.h b/clients/drcachesim/tracer/instru.h
index 78bc7cbe4b704a7af196bbb9d43b42e69d5b717e..4e26a34a59153b4b6bdfad0904eba4e1d974c8f5 100644
--- a/clients/drcachesim/tracer/instru.h
+++ b/clients/drcachesim/tracer/instru.h
@@ -399,7 +399,7 @@ public:
                 bool repstr_expanded) override;
 
     static bool
-    custom_module_data(void *(*load_cb)(module_data_t *module),
+    custom_module_data(void *(*load_cb)(module_data_t *module, int seg_idx),
                        int (*print_cb)(void *data, char *dst, size_t max_len),
                        void (*free_cb)(void *data));
 
@@ -460,11 +460,11 @@ private:
 
     // Custom module fields are global (since drmodtrack's support is global, we don't
     // try to pass void* user data params through).
-    static void *(*user_load_)(module_data_t *module);
+    static void *(*user_load_)(module_data_t *module, int seg_idx);
     static int (*user_print_)(void *data, char *dst, size_t max_len);
     static void (*user_free_)(void *data);
     static void *
-    load_custom_module_data(module_data_t *module);
+    load_custom_module_data(module_data_t *module, int seg_idx);
     static int
     print_custom_module_data(void *data, char *dst, size_t max_len);
     static void
diff --git a/clients/drcachesim/tracer/instru_offline.cpp b/clients/drcachesim/tracer/instru_offline.cpp
index bc92db6ca6f87236d56edf346787bef6ed97b282..7c0dfe8d2c9b55cd55e51f2f167d1ae51ac2bc6f 100644
--- a/clients/drcachesim/tracer/instru_offline.cpp
+++ b/clients/drcachesim/tracer/instru_offline.cpp
@@ -1,5 +1,5 @@
 /* **********************************************************
- * Copyright (c) 2016-2020 Google, Inc.  All rights reserved.
+ * Copyright (c) 2016-2021 Google, Inc.  All rights reserved.
  * **********************************************************/
 
 /*
@@ -47,7 +47,7 @@
 
 static const ptr_uint_t MAX_INSTR_COUNT = 64 * 1024;
 
-void *(*offline_instru_t::user_load_)(module_data_t *module);
+void *(*offline_instru_t::user_load_)(module_data_t *module, int seg_idx);
 int (*offline_instru_t::user_print_)(void *data, char *dst, size_t max_len);
 void (*offline_instru_t::user_free_)(void *data);
 
@@ -115,11 +115,11 @@ offline_instru_t::~offline_instru_t()
 }
 
 void *
-offline_instru_t::load_custom_module_data(module_data_t *module)
+offline_instru_t::load_custom_module_data(module_data_t *module, int seg_idx)
 {
     void *user_data = nullptr;
     if (user_load_ != nullptr)
-        user_data = (*user_load_)(module);
+        user_data = (*user_load_)(module, seg_idx);
     const char *name = dr_module_preferred_name(module);
     // For vdso we include the entire contents so we can decode it during
     // post-processing.
@@ -129,8 +129,17 @@ offline_instru_t::load_custom_module_data(module_data_t *module)
           strstr(name, "linux-vdso.so") == name)) ||
         (module->names.file_name != NULL && strcmp(name, "[vdso]") == 0)) {
         void *alloc = dr_global_alloc(sizeof(custom_module_data_t));
-        return new (alloc) custom_module_data_t((const char *)module->start,
-                                                module->end - module->start, user_data);
+#ifdef WINDOWS
+        byte *start = module->start;
+        byte *end = module->end;
+#else
+        byte *start =
+            (module->num_segments > 0) ? module->segments[seg_idx].start : module->start;
+        byte *end =
+            (module->num_segments > 0) ? module->segments[seg_idx].end : module->end;
+#endif
+        return new (alloc)
+            custom_module_data_t((const char *)start, end - start, user_data);
     } else if (user_data != nullptr) {
         void *alloc = dr_global_alloc(sizeof(custom_module_data_t));
         return new (alloc) custom_module_data_t(nullptr, 0, user_data);
@@ -190,7 +199,7 @@ offline_instru_t::free_custom_module_data(void *data)
 }
 
 bool
-offline_instru_t::custom_module_data(void *(*load_cb)(module_data_t *module),
+offline_instru_t::custom_module_data(void *(*load_cb)(module_data_t *module, int seg_idx),
                                      int (*print_cb)(void *data, char *dst,
                                                      size_t max_len),
                                      void (*free_cb)(void *data))
diff --git a/clients/drcachesim/tracer/tracer.cpp b/clients/drcachesim/tracer/tracer.cpp
index b409dcf4f2e68a66dbb054a673b5a8954967366b..ef49eeac9b0ed2459da45a8c94895e0874311454 100644
--- a/clients/drcachesim/tracer/tracer.cpp
+++ b/clients/drcachesim/tracer/tracer.cpp
@@ -1,5 +1,5 @@
 /* ******************************************************************************
- * Copyright (c) 2011-2020 Google, Inc.  All rights reserved.
+ * Copyright (c) 2011-2021 Google, Inc.  All rights reserved.
  * Copyright (c) 2010 Massachusetts Institute of Technology  All rights reserved.
  * ******************************************************************************/
 
@@ -282,7 +282,7 @@ drmemtrace_get_funclist_path(OUT const char **path)
 }
 
 drmemtrace_status_t
-drmemtrace_custom_module_data(void *(*load_cb)(module_data_t *module),
+drmemtrace_custom_module_data(void *(*load_cb)(module_data_t *module, int seg_idx),
                               int (*print_cb)(void *data, char *dst, size_t max_len),
                               void (*free_cb)(void *data))
 {
diff --git a/ext/drcovlib/drcovlib.h b/ext/drcovlib/drcovlib.h
index 37725c3ee4667a6956e2d6868778281a140f44ce..6cebe74ff1919ab5e94fd9a2cdb833633590b78a 100644
--- a/ext/drcovlib/drcovlib.h
+++ b/ext/drcovlib/drcovlib.h
@@ -1,5 +1,5 @@
 /* **********************************************************
- * Copyright (c) 2016-2017 Google, Inc.   All rights reserved.
+ * Copyright (c) 2016-2021 Google, Inc.   All rights reserved.
  * **********************************************************/
 
 /*
@@ -378,15 +378,16 @@ DR_EXPORT
  * and writes the parsed data to its output parameter, which can subsequently be
  * retrieved from drmodtrack_offline_lookup()'s \p custom output parameter.
  *
- * If a module contains non-contiguous segments, \p load_cb is called
- * only once, and the resulting custom field is shared among all
- * separate entries returned by drmodtrack_offline_lookup().
+ * If a module contains multiple segments, \p load_cb is called
+ * multiple times, once for each segment, with \p seg_idx indicating the segment.
+ * Each segment stores its own custom field, and each callback is invoked separately
+ * for each segment.
  *
  * Only one value for each callback is supported.  Calling this routine again
  * with a different value will replace the existing callbacks.
  */
 drcovlib_status_t
-drmodtrack_add_custom_data(void *(*load_cb)(module_data_t *module),
+drmodtrack_add_custom_data(void *(*load_cb)(module_data_t *module, int seg_idx),
                            int (*print_cb)(void *data, char *dst, size_t max_len),
                            const char *(*parse_cb)(const char *src, OUT void **data),
                            void (*free_cb)(void *data));
diff --git a/ext/drcovlib/modules.c b/ext/drcovlib/modules.c
index 286277ae639a7ed5b5df2625e38cb540177aae7b..5facc34a2285ae4150702e9a72ddb9cfb6129adb 100644
--- a/ext/drcovlib/modules.c
+++ b/ext/drcovlib/modules.c
@@ -80,7 +80,7 @@ static int tls_idx = -1;
 static module_table_t module_table;
 
 /* Custom per-module field support. */
-static void *(*module_load_cb)(module_data_t *module);
+static void *(*module_load_cb)(module_data_t *module, int seg_idx);
 static int (*module_print_cb)(void *data, char *dst, size_t max_len);
 static const char *(*module_parse_cb)(const char *src, OUT void **data);
 static void (*module_free_cb)(void *data);
@@ -117,11 +117,10 @@ static void
 module_table_entry_free(void *tofree)
 {
     module_entry_t *entry = (module_entry_t *)tofree;
-    if (entry->id == entry->containing_id) {
-        if (module_free_cb != NULL)
-            module_free_cb(((module_entry_t *)entry)->custom);
+    if (module_free_cb != NULL)
+        module_free_cb(((module_entry_t *)entry)->custom);
+    if (entry->id == entry->containing_id) /* Else a sub-entry which shares data. */
         dr_free_module_data(((module_entry_t *)entry)->data);
-    } /* else a sub-entry which shares custom and data */
     dr_global_free(entry, sizeof(module_entry_t));
 }
 
@@ -190,7 +189,7 @@ event_module_load(void *drcontext, const module_data_t *data, bool loaded)
         entry->unload = false;
         entry->data = dr_copy_module_data(data);
         if (module_load_cb != NULL)
-            entry->custom = module_load_cb(entry->data);
+            entry->custom = module_load_cb(entry->data, 0);
         drvector_append(&module_table.vector, entry);
 #ifndef WINDOWS
         entry->offset = data->segments[0].offset;
@@ -208,9 +207,9 @@ event_module_load(void *drcontext, const module_data_t *data, bool loaded)
             sub_entry->start = data->segments[j].start;
             sub_entry->end = data->segments[j].end;
             sub_entry->unload = false;
-            /* These fields are shared. */
-            sub_entry->data = entry->data;
-            sub_entry->custom = entry->custom;
+            sub_entry->data = entry->data; /* Shared among all segments. */
+            if (module_load_cb != NULL)
+                sub_entry->custom = module_load_cb(sub_entry->data, j);
             sub_entry->offset = data->segments[j].offset;
             drvector_append(&module_table.vector, sub_entry);
             global_module_cache_add(module_table.cache, sub_entry);
@@ -792,7 +791,7 @@ drmodtrack_offline_exit(void *handle)
 }
 
 drcovlib_status_t
-drmodtrack_add_custom_data(void *(*load_cb)(module_data_t *module),
+drmodtrack_add_custom_data(void *(*load_cb)(module_data_t *module, int seg_idx),
                            int (*print_cb)(void *data, char *dst, size_t max_len),
                            const char *(*parse_cb)(const char *src, OUT void **data),
                            void (*free_cb)(void *data))
diff --git a/suite/tests/client-interface/drmodtrack-test.dll.cpp b/suite/tests/client-interface/drmodtrack-test.dll.cpp
index 7f41686ce0a3a223148923173a1c66b44c64cc5d..0815620ef856671fb683eba0f513f837db9f7ad7 100644
--- a/suite/tests/client-interface/drmodtrack-test.dll.cpp
+++ b/suite/tests/client-interface/drmodtrack-test.dll.cpp
@@ -1,5 +1,5 @@
 /* **********************************************************
- * Copyright (c) 2017-2019 Google, Inc.  All rights reserved.
+ * Copyright (c) 2017-2021 Google, Inc.  All rights reserved.
  * **********************************************************/
 
 /*
@@ -55,8 +55,12 @@
 static client_id_t client_id;
 
 static void *
-load_cb(module_data_t *module)
+load_cb(module_data_t *module, int seg_idx)
 {
+#ifndef WINDOWS
+    if (seg_idx > 0)
+        return (void *)module->segments[seg_idx].start;
+#endif
     return (void *)module->start;
 }
 
@@ -189,8 +193,7 @@ event_exit(void)
         };
         res = drmodtrack_offline_lookup(modhandle, i, &info);
         CHECK(res == DRCOVLIB_SUCCESS, "lookup failed");
-        CHECK(((app_pc)info.custom) == info.start || info.containing_index != i,
-              "custom field doesn't match");
+        CHECK(((app_pc)info.custom) == info.start, "custom field doesn't match");
         CHECK(info.index == i, "index field doesn't match");
 #ifndef WINDOWS
         if (info.struct_size > offsetof(drmodtrack_info_t, offset)) {