From: Paul Floyd Date: Thu, 9 Jun 2022 20:03:04 +0000 (+0200) Subject: Bug 452802 Handle lld 9+ split RW PT_LOAD segments correctly X-Git-Tag: VALGRIND_3_20_0~45 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7844752299b5472b21fc4df765d4cffdf92c6c3d;p=thirdparty%2Fvalgrind.git Bug 452802 Handle lld 9+ split RW PT_LOAD segments correctly Many changes mostly related to modifying VG_(di_notify_mmap)( Addr a, Bool allow_SkFileV, Int use_fd ) so that instead of triggering debuginfo reading after seeing one RX PT_LOAD and 1 RW PT_LOAD it can handle either 1 or 2 RW PT_LOADs. --- diff --git a/NEWS b/NEWS index a0cf73eaff..1f92c25e3d 100644 --- a/NEWS +++ b/NEWS @@ -41,6 +41,7 @@ are not entered into bugzilla tend to get forgotten about or ignored. 452779 Valgrind fails to build on FreeBSD 13.0 with llvm-devel (15.0.0) 453055 shared_timed_mutex drd test fails with "Lock shared failed" message 453602 Missing command line option to enable/disable debuginfod +452802 Handle lld 9+ split RW PT_LOAD segments correctly To see details of a given bug, visit https://bugs.kde.org/show_bug.cgi?id=XXXXXX diff --git a/coregrind/m_debuginfo/debuginfo.c b/coregrind/m_debuginfo/debuginfo.c index 60f9ea195d..34a2ea8ccb 100644 --- a/coregrind/m_debuginfo/debuginfo.c +++ b/coregrind/m_debuginfo/debuginfo.c @@ -665,7 +665,7 @@ static void check_CFSI_related_invariants ( const DebugInfo* di ) been successfully read. And that shouldn't happen until we have both a r-x and rw- mapping for the object. Hence: */ vg_assert(di->fsm.have_rx_map); - vg_assert(di->fsm.have_rw_map); + vg_assert(di->fsm.rw_map_count); for (i = 0; i < VG_(sizeXA)(di->fsm.maps); i++) { const DebugInfoMapping* map = VG_(indexXA)(di->fsm.maps, i); /* We are interested in r-x mappings only */ @@ -1024,16 +1024,96 @@ static ULong di_notify_ACHIEVE_ACCEPT_STATE ( struct _DebugInfo* di ) /* Notify the debuginfo system about a new mapping. This is the way - new debug information gets loaded. If allow_SkFileV is True, it - will try load debug info if the mapping at 'a' belongs to Valgrind; - whereas normally (False) it will not do that. This allows us to - carefully control when the thing will read symbols from the - Valgrind executable itself. + new debug information gets loaded. + + redelf -e will output something like + + readelf -e says + + Program Headers: + Type Offset VirtAddr PhysAddr + FileSiz MemSiz Flg Align + PHDR 0x0000000000000040 0x0000000000200040 0x0000000000200040 + 0x0000000000000268 0x0000000000000268 R 0x8 + INTERP 0x00000000000002a8 0x00000000002002a8 0x00000000002002a8 + 0x0000000000000015 0x0000000000000015 R 0x1 + [Requesting program interpreter: /libexec/ld-elf.so.1] + LOAD 0x0000000000000000 0x0000000000200000 0x0000000000200000 + 0x0000000000002acc 0x0000000000002acc R 0x1000 + LOAD 0x0000000000002ad0 0x0000000000203ad0 0x0000000000203ad0 + 0x0000000000004a70 0x0000000000004a70 R E 0x1000 + LOAD 0x0000000000007540 0x0000000000209540 0x0000000000209540 + 0x00000000000001d8 0x00000000000001d8 RW 0x1000 + LOAD 0x0000000000007720 0x000000000020a720 0x000000000020a720 + 0x00000000000002b8 0x00000000000005a0 RW 0x1000 + DYNAMIC 0x0000000000007570 0x0000000000209570 0x0000000000209570 + 0x00000000000001a0 0x00000000000001a0 RW 0x8 + GNU_RELRO 0x0000000000007540 0x0000000000209540 0x0000000000209540 + 0x00000000000001d8 0x00000000000001d8 R 0x1 + GNU_EH_FRAME 0x0000000000002334 0x0000000000202334 0x0000000000202334 + 0x000000000000012c 0x000000000000012c R 0x4 + GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 + 0x0000000000000000 0x0000000000000000 RW 0 + NOTE 0x00000000000002c0 0x00000000002002c0 0x00000000002002c0 + 0x0000000000000048 0x0000000000000048 R 0x4 + + This function will be called for the "LOAD" segments above. + + This function gets called from 2 contexts + + "HOST TRIGGERED" + + 1a. For the tool exe and tool/core shared libs. These are already + mmap'd when the host starts so we look at something like the + /proc filesystem to get the mapping after the event and build + up the NSegments from that. + + 1b. Then the host loads ld.so and the guest exe. This is done in + the sequence + load_client -> VG_(do_exec) -> VG_(do_exec_inner) -> + exe_handlers->load_fn ( == VG_(load_ELF) ). + + This does the mmap'ing and creats the associated NSegments. + + The NSegments may get merged, (see maybe_merge_nsegments) + so there could be more PT_LOADs than there are NSegments. + VG_(di_notify_mmap) is called by iterating over the + NSegments + + "GUEST TRIGGERED" + + 2. When the guest loads any further shared libs (libc, + other dependencies, dlopens) using mmap. + + There are a few variations for syswraps/platforms. + + In this case the NSegment could possibly be merged, + but that is irrelevant because di_notify_mmap is being + called directy on the mmap result. + + If allow_SkFileV is True, it will try load debug info if the + mapping at 'a' belongs to Valgrind; whereas normally (False) + it will not do that. This allows us to carefully control when + the thing will read symbols from the Valgrind executable itself. If use_fd is not -1, that is used instead of the filename; this avoids perturbing fcntl locks, which are released by simply re-opening and closing the same file (even via different fd!). + Read-only mappings will be ignored. + There may be 1 or 2 RW mappings. + There will also be 1 RX mapping. + + If there is no RX or no RW mapping then we will not attempt to + read debuginfo for the file. + + In order to know whether there are 1 or 2 RW mappings we + need to check the ELF headers. And in the case that we + detect 2 RW mappings we need to double check that they + aren't contiguous in memory resulting in merged NSegemnts. + + This does not apply to Darwin which just checks the Mach-O header + If a call to VG_(di_notify_mmap) causes debug info to be read, then the returned ULong is an abstract handle which can later be used to refer to the debuginfo read as a result of this specific mapping, @@ -1044,12 +1124,19 @@ static ULong di_notify_ACHIEVE_ACCEPT_STATE ( struct _DebugInfo* di ) ULong VG_(di_notify_mmap)( Addr a, Bool allow_SkFileV, Int use_fd ) { NSegment const * seg; + Int rw_load_count; const HChar* filename; Bool is_rx_map, is_rw_map, is_ro_map; + DebugInfo* di; Int actual_fd, oflags; +#if defined(VGO_darwin) SysRes preadres; HChar buf1k[1024]; +#else + Bool elf_ok; +#endif + const Bool debug = VG_(debugLog_getLevel)() >= 3; SysRes statres; struct vg_stat statbuf; @@ -1211,9 +1298,12 @@ ULong VG_(di_notify_mmap)( Addr a, Bool allow_SkFileV, Int use_fd ) return 0; #endif +#if defined(VGO_darwin) /* Peer at the first few bytes of the file, to see if it is an ELF */ /* object file. Ignore the file if we do not have read permission. */ VG_(memset)(buf1k, 0, sizeof(buf1k)); +#endif + oflags = VKI_O_RDONLY; # if defined(VKI_O_LARGEFILE) oflags |= VKI_O_LARGEFILE; @@ -1237,6 +1327,7 @@ ULong VG_(di_notify_mmap)( Addr a, Bool allow_SkFileV, Int use_fd ) actual_fd = use_fd; } +#if defined(VGO_darwin) preadres = VG_(pread)( actual_fd, buf1k, sizeof(buf1k), 0 ); if (use_fd == -1) { VG_(close)( actual_fd ); @@ -1246,20 +1337,33 @@ ULong VG_(di_notify_mmap)( Addr a, Bool allow_SkFileV, Int use_fd ) DebugInfo fake_di; VG_(memset)(&fake_di, 0, sizeof(fake_di)); fake_di.fsm.filename = ML_(dinfo_strdup)("di.debuginfo.nmm", filename); - ML_(symerr)(&fake_di, True, "can't read file to inspect ELF header"); + ML_(symerr)(&fake_di, True, "can't read file to inspect Mach-O headers"); return 0; } if (sr_Res(preadres) == 0) return 0; vg_assert(sr_Res(preadres) > 0 && sr_Res(preadres) <= sizeof(buf1k) ); +#endif /* We're only interested in mappings of object files. */ # if defined(VGO_linux) || defined(VGO_solaris) || defined(VGO_freebsd) - if (!ML_(is_elf_object_file)( buf1k, (SizeT)sr_Res(preadres), False )) + + rw_load_count = 0; + + elf_ok = ML_(check_elf_and_get_rw_loads) ( actual_fd, filename, &rw_load_count ); + + if (use_fd == -1) { + VG_(close)( actual_fd ); + } + + if (!elf_ok) { return 0; + } + # elif defined(VGO_darwin) if (!ML_(is_macho_object_file)( buf1k, (SizeT)sr_Res(preadres) )) return 0; + rw_load_count = 1; # else # error "unknown OS" # endif @@ -1311,15 +1415,20 @@ ULong VG_(di_notify_mmap)( Addr a, Bool allow_SkFileV, Int use_fd ) /* Update flags about what kind of mappings we've already seen. */ di->fsm.have_rx_map |= is_rx_map; - di->fsm.have_rw_map |= is_rw_map; + /* This is a bit of a hack, using a Bool as a counter */ + if (is_rw_map) + ++di->fsm.rw_map_count; di->fsm.have_ro_map |= is_ro_map; /* So, finally, are we in an accept state? */ vg_assert(!di->have_dinfo); - if (di->fsm.have_rx_map && di->fsm.have_rw_map) { + if (di->fsm.have_rx_map && + rw_load_count >= 1 && + di->fsm.rw_map_count == rw_load_count) { /* Ok, so, finally, we found what we need, and we haven't already read debuginfo for this object. So let's do so now. Yee-ha! */ + if (debug) VG_(dmsg)("di_notify_mmap-5: " "achieved accept state for %s\n", filename); @@ -1416,7 +1525,7 @@ void VG_(di_notify_vm_protect)( Addr a, SizeT len, UInt prot ) continue; /* need to have a r-- mapping for this object */ if (di->fsm.have_rx_map) continue; /* rx- mapping already exists */ - if (!di->fsm.have_rw_map) + if (!di->fsm.rw_map_count) continue; /* need to have a rw- mapping */ /* Try to find a mapping matching the memory area. */ for (i = 0; i < VG_(sizeXA)(di->fsm.maps); i++) { @@ -1454,7 +1563,7 @@ void VG_(di_notify_vm_protect)( Addr a, SizeT len, UInt prot ) } /* Check if we're now in an accept state and read debuginfo. Finally. */ - if (di->fsm.have_rx_map && di->fsm.have_rw_map && !di->have_dinfo) { + if (di->fsm.have_rx_map && di->fsm.rw_map_count && !di->have_dinfo) { if (debug) VG_(dmsg)("di_notify_vm_protect-5: " "achieved accept state for %s\n", di->fsm.filename); @@ -1669,7 +1778,7 @@ void VG_(di_notify_pdb_debuginfo)( Int fd_obj, Addr avma_obj, { DebugInfo* di = find_or_create_DebugInfo_for(exename); /* this di must be new, since we just nuked any old stuff in the range */ - vg_assert(di && !di->fsm.have_rx_map && !di->fsm.have_rw_map); + vg_assert(di && !di->fsm.have_rx_map && !di->fsm.rw_map_count); vg_assert(!di->have_dinfo); /* don't set up any of the di-> fields; let diff --git a/coregrind/m_debuginfo/image.c b/coregrind/m_debuginfo/image.c index ebe6dfcfe8..28dfd0b472 100644 --- a/coregrind/m_debuginfo/image.c +++ b/coregrind/m_debuginfo/image.c @@ -874,6 +874,49 @@ DiImage* ML_(img_from_local_file)(const HChar* fullpath) return img; } +/* As above, but uses fd rather than filename */ +DiImage* ML_(img_from_fd)(Int fd, const HChar* fullpath) +{ + struct vg_stat stat_buf; + DiOffT size; + + if (VG_(fstat)(fd, &stat_buf) != 0) { + return NULL; + } + + size = stat_buf.size; + if (size == 0 || size == DiOffT_INVALID + || /* size is unrepresentable as a SizeT */ + size != (DiOffT)(SizeT)(size)) { + return NULL; + } + + DiImage* img = ML_(dinfo_zalloc)("di.image.ML_iflf.1", sizeof(DiImage)); + img->source.is_local = True; + img->source.fd = fd; + img->size = size; + img->real_size = size; + img->ces_used = 0; + img->source.name = ML_(dinfo_strdup)("di.image.ML_iflf.2", fullpath); + img->cslc = NULL; + img->cslc_size = 0; + img->cslc_used = 0; + /* img->ces is already zeroed out */ + vg_assert(img->source.fd >= 0); + + /* Force the zeroth entry to be the first chunk of the file. + That's likely to be the first part that's requested anyway, and + loading it at this point forcing img->cent[0] to always be + non-empty, thereby saving us an is-it-empty check on the fast + path in get(). */ + UInt entNo = alloc_CEnt(img, CACHE_ENTRY_SIZE, False/*!fromC*/); + vg_assert(entNo == 0); + set_CEnt(img, 0, 0); + + return img; +} + + /* Create an image from a file on a remote debuginfo server. This is more complex. There are lots of ways in which it can fail. */ @@ -1007,20 +1050,9 @@ DiOffT ML_(img_mark_compressed_part)(DiImage* img, DiOffT offset, SizeT szC, return ret; } -void ML_(img_done)(DiImage* img) +void ML_(img_free)(DiImage* img) { vg_assert(img != NULL); - if (img->source.is_local) { - /* Close the file; nothing else to do. */ - vg_assert(img->source.session_id == 0); - VG_(close)(img->source.fd); - } else { - /* Close the socket. The server can detect this and will scrub - the connection when it happens, so there's no need to tell it - explicitly by sending it a "CLOSE" message, or any such. */ - vg_assert(img->source.session_id != 0); - VG_(close)(img->source.fd); - } /* Free up the cache entries, ultimately |img| itself. */ UInt i; @@ -1037,6 +1069,26 @@ void ML_(img_done)(DiImage* img) ML_(dinfo_free)(img); } +void ML_(img_done)(DiImage* img) +{ + vg_assert(img != NULL); + if (img->source.is_local) { + /* Close the file; nothing else to do. */ + vg_assert(img->source.session_id == 0); + VG_(close)(img->source.fd); + } else { + /* Close the socket. The server can detect this and will scrub + the connection when it happens, so there's no need to tell it + explicitly by sending it a "CLOSE" message, or any such. */ + vg_assert(img->source.session_id != 0); + VG_(close)(img->source.fd); + } + + ML_(img_free)(img); +} + + + DiOffT ML_(img_size)(const DiImage* img) { vg_assert(img != NULL); diff --git a/coregrind/m_debuginfo/priv_image.h b/coregrind/m_debuginfo/priv_image.h index f7413a9084..a49846f149 100644 --- a/coregrind/m_debuginfo/priv_image.h +++ b/coregrind/m_debuginfo/priv_image.h @@ -59,6 +59,8 @@ typedef ULong DiOffT; if it fails, for whatever reason. */ DiImage* ML_(img_from_local_file)(const HChar* fullpath); +DiImage* ML_(img_from_fd)(Int fd, const HChar* fullpath); + /* Create an image by connecting to a Valgrind debuginfo server (auxprogs/valgrind-di-server.c). |filename| contains the object name to ask for; it must be a plain filename, not absolute, not a @@ -69,6 +71,9 @@ DiImage* ML_(img_from_local_file)(const HChar* fullpath); DiImage* ML_(img_from_di_server)(const HChar* filename, const HChar* serverAddr); +/* Free memory allocated for image. */ +void ML_(img_free)(DiImage*); + /* Destroy an existing image. */ void ML_(img_done)(DiImage*); diff --git a/coregrind/m_debuginfo/priv_readelf.h b/coregrind/m_debuginfo/priv_readelf.h index 2bee615ab5..57aa0cc3f3 100644 --- a/coregrind/m_debuginfo/priv_readelf.h +++ b/coregrind/m_debuginfo/priv_readelf.h @@ -52,6 +52,8 @@ extern Bool ML_(is_elf_object_file)( const void* image, SizeT n_image, */ extern Bool ML_(read_elf_debug_info) ( DebugInfo* di ); +extern Bool ML_(check_elf_and_get_rw_loads) ( Int fd, const HChar* filename, Int * rw_load_count ); + #endif /* ndef __PRIV_READELF_H */ diff --git a/coregrind/m_debuginfo/priv_storage.h b/coregrind/m_debuginfo/priv_storage.h index ae44ca34e5..f44ab43ffe 100644 --- a/coregrind/m_debuginfo/priv_storage.h +++ b/coregrind/m_debuginfo/priv_storage.h @@ -585,7 +585,7 @@ struct _DebugInfoFSM HChar* dbgname; /* in mallocville (VG_AR_DINFO) */ XArray* maps; /* XArray of DebugInfoMapping structs */ Bool have_rx_map; /* did we see a r?x mapping yet for the file? */ - Bool have_rw_map; /* did we see a rw? mapping yet for the file? */ + Int rw_map_count; /* count of w? mappings seen (may be > 1 ) */ Bool have_ro_map; /* did we see a r-- mapping yet for the file? */ }; diff --git a/coregrind/m_debuginfo/readelf.c b/coregrind/m_debuginfo/readelf.c index ea9c80415b..b4edb4fe85 100644 --- a/coregrind/m_debuginfo/readelf.c +++ b/coregrind/m_debuginfo/readelf.c @@ -1182,10 +1182,10 @@ void read_and_set_osrel(DiImage* img) SizeT newlen = sizeof(osrel); Int error = VG_(sysctl)(name, 4, NULL, NULL, &osrel, newlen); if (error == -1) { - VG_(message)(Vg_DebugMsg, "Warning: failed to set osrel for current process with value %d\n", osrel); + VG_(message)(Vg_DebugMsg, "Warning: failed to set osrel for current process with value %u\n", osrel); } else { if (VG_(clo_verbosity) > 1) { - VG_(message)(Vg_DebugMsg, "Set osrel for current process with value %d\n", osrel); + VG_(message)(Vg_DebugMsg, "Set osrel for current process with value %u\n", osrel); } } } @@ -1924,7 +1924,7 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) vg_assert(di); vg_assert(di->fsm.have_rx_map == True); - vg_assert(di->fsm.have_rw_map == True); + vg_assert(di->fsm.rw_map_count >= 1); vg_assert(di->have_dinfo == False); vg_assert(di->fsm.filename); vg_assert(!di->symtab); @@ -2120,11 +2120,6 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) item.svma_limit = a_phdr.p_vaddr + a_phdr.p_memsz; item.bias = map->avma - map->foff + a_phdr.p_offset - a_phdr.p_vaddr; -#if (FREEBSD_VERS >= FREEBSD_12_2) - if ((long long int)item.bias < 0LL) { - item.bias = 0; - } -#endif if (map->rw && (a_phdr.p_flags & (PF_R | PF_W)) == (PF_R | PF_W)) { @@ -2264,9 +2259,10 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) } for (i = 0; i < VG_(sizeXA)(di->fsm.maps); i++) { const DebugInfoMapping* map = VG_(indexXA)(di->fsm.maps, i); - if (map->rw) + if (map->rw) { TRACE_SYMTAB("rw: at %#lx are mapped foffsets %lld .. %lld\n", map->avma, (Long)map->foff, (Long)(map->foff + map->size - 1) ); + } } TRACE_SYMTAB("rw: contains these svma regions:\n"); for (i = 0; i < VG_(sizeXA)(svma_ranges); i++) { @@ -2290,28 +2286,34 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) UInt alyn = a_shdr.sh_addralign; Bool nobits = a_shdr.sh_type == SHT_NOBITS; /* Look through our collection of info obtained from the PT_LOAD - headers, and make 'inrx' and 'inrw' point to the first entry + headers, and make 'inrx' and 'inrw1' point to the first entry in each that intersects 'avma'. If in each case none is found, leave the relevant pointer at NULL. */ RangeAndBias* inrx = NULL; - RangeAndBias* inrw = NULL; + RangeAndBias* inrw1 = NULL; + /* Depending on the link editro there may be two RW PT_LOAD headers + * If so this will point to the seond one */ + RangeAndBias* inrw2 = NULL; + /* used to switch between inrw1 and inrw2 */ + RangeAndBias* inrw; + for (j = 0; j < VG_(sizeXA)(svma_ranges); j++) { RangeAndBias* rng = VG_(indexXA)(svma_ranges, j); if (svma >= rng->svma_base && svma < rng->svma_limit) { if (!inrx && rng->exec) { inrx = rng; - } else if (!inrw && !rng->exec) { - inrw = rng; + } else if (!inrw1 && !rng->exec) { + inrw1 = rng; + } else if (!inrw2 && !rng->exec) { + inrw2 = rng; } - if (inrx && inrw) - break; } } - TRACE_SYMTAB(" [sec %2ld] %s %s al%4u foff %6lld .. %6lld " + TRACE_SYMTAB(" [sec %2ld] %s %s %s al%4u foff %6lld .. %6lld " " svma %p name \"%s\"\n", - i, inrx ? "rx" : " ", inrw ? "rw" : " ", alyn, - (Long) foff, (size == 0) ? (Long)foff : (Long)(foff+size-1), + i, inrx ? "rx" : " ", inrw1 ? "rw" : " ", inrw2 ? "rw" : " ", + alyn, (Long) foff, (size == 0) ? (Long)foff : (Long)(foff+size-1), (void *) svma, name); /* Check for sane-sized segments. SHT_NOBITS sections have zero @@ -2350,6 +2352,8 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) /* Find avma-s for: .text .data .sdata .rodata .bss .sbss .plt .got .opd and .eh_frame */ + /* In inrw2 is non-NULL then it will be used for .data .got.plt .bss */ + /* Accept .text where mapped as rx (code), even if zero-sized */ if (0 == VG_(strcmp)(name, ".text")) { if (inrx && !di->text_present) { @@ -2380,6 +2384,13 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) "%#lx .. %#lx\n", svma, svma + size - 1); } else # endif /* SOLARIS_PT_SUNDWTRACE_THRP */ + + if (inrw2) { + inrw = inrw2; + } else { + inrw = inrw1; + } + if (inrw && !di->data_present) { di->data_present = True; di->data_svma = svma; @@ -2402,14 +2413,14 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) /* Accept .sdata where mapped as rw (data) */ if (0 == VG_(strcmp)(name, ".sdata")) { - if (inrw && !di->sdata_present) { + if (inrw1 && !di->sdata_present) { di->sdata_present = True; di->sdata_svma = svma; - di->sdata_avma = svma + inrw->bias; + di->sdata_avma = svma + inrw1->bias; di->sdata_size = size; - di->sdata_bias = inrw->bias; + di->sdata_bias = inrw1->bias; di->sdata_debug_svma = svma; - di->sdata_debug_bias = inrw->bias; + di->sdata_debug_bias = inrw1->bias; TRACE_SYMTAB("acquiring .sdata svma = %#lx .. %#lx\n", di->sdata_svma, di->sdata_svma + di->sdata_size - 1); @@ -2434,10 +2445,10 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) di->rodata_avma += inrx->bias; di->rodata_bias = inrx->bias; di->rodata_debug_bias = inrx->bias; - } else if (inrw) { - di->rodata_avma += inrw->bias; - di->rodata_bias = inrw->bias; - di->rodata_debug_bias = inrw->bias; + } else if (inrw1) { + di->rodata_avma += inrw1->bias; + di->rodata_bias = inrw1->bias; + di->rodata_debug_bias = inrw1->bias; } else { BAD(".rodata"); } @@ -2456,15 +2467,15 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) } if (0 == VG_(strcmp)(name, ".dynbss")) { - if (inrw && !di->bss_present) { + if (inrw1 && !di->bss_present) { dynbss_present = True; di->bss_present = True; di->bss_svma = svma; - di->bss_avma = svma + inrw->bias; + di->bss_avma = svma + inrw1->bias; di->bss_size = size; - di->bss_bias = inrw->bias; + di->bss_bias = inrw1->bias; di->bss_debug_svma = svma; - di->bss_debug_bias = inrw->bias; + di->bss_debug_bias = inrw1->bias; TRACE_SYMTAB("acquiring .dynbss svma = %#lx .. %#lx\n", di->bss_svma, di->bss_svma + di->bss_size - 1); @@ -2478,6 +2489,13 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) /* Accept .bss where mapped as rw (data), even if zero-sized */ if (0 == VG_(strcmp)(name, ".bss")) { + + if (inrw2) { + inrw = inrw2; + } else { + inrw = inrw1; + } + if (inrw && dynbss_present) { vg_assert(di->bss_present); dynbss_present = False; @@ -2544,15 +2562,15 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) } if (0 == VG_(strcmp)(name, ".sdynbss")) { - if (inrw && !di->sbss_present) { + if (inrw1 && !di->sbss_present) { sdynbss_present = True; di->sbss_present = True; di->sbss_svma = svma; - di->sbss_avma = svma + inrw->bias; + di->sbss_avma = svma + inrw1->bias; di->sbss_size = size; - di->sbss_bias = inrw->bias; + di->sbss_bias = inrw1->bias; di->sbss_debug_svma = svma; - di->sbss_debug_bias = inrw->bias; + di->sbss_debug_bias = inrw1->bias; TRACE_SYMTAB("acquiring .sdynbss svma = %#lx .. %#lx\n", di->sbss_svma, di->sbss_svma + di->sbss_size - 1); @@ -2566,7 +2584,7 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) /* Accept .sbss where mapped as rw (data) */ if (0 == VG_(strcmp)(name, ".sbss")) { - if (inrw && sdynbss_present) { + if (inrw1 && sdynbss_present) { vg_assert(di->sbss_present); sdynbss_present = False; vg_assert(di->sbss_svma + di->sbss_size == svma); @@ -2574,18 +2592,18 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) TRACE_SYMTAB("acquiring .sbss svma = %#lx .. %#lx\n", svma, svma + size - 1); TRACE_SYMTAB("acquiring .sbss avma = %#lx .. %#lx\n", - svma + inrw->bias, svma + inrw->bias + size - 1); + svma + inrw1->bias, svma + inrw1->bias + size - 1); TRACE_SYMTAB("acquiring .sbss bias = %#lx\n", (UWord)di->sbss_bias); } else - if (inrw && !di->sbss_present) { + if (inrw1 && !di->sbss_present) { di->sbss_present = True; di->sbss_svma = svma; - di->sbss_avma = svma + inrw->bias; + di->sbss_avma = svma + inrw1->bias; di->sbss_size = size; - di->sbss_bias = inrw->bias; + di->sbss_bias = inrw1->bias; di->sbss_debug_svma = svma; - di->sbss_debug_bias = inrw->bias; + di->sbss_debug_bias = inrw1->bias; TRACE_SYMTAB("acquiring .sbss svma = %#lx .. %#lx\n", di->sbss_svma, di->sbss_svma + di->sbss_size - 1); @@ -2600,9 +2618,9 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) /* Accept .got where mapped as rw (data) */ if (0 == VG_(strcmp)(name, ".got")) { - if (inrw && !di->got_present) { + if (inrw1 && !di->got_present) { di->got_present = True; - di->got_avma = svma + inrw->bias; + di->got_avma = svma + inrw1->bias; di->got_size = size; TRACE_SYMTAB("acquiring .got avma = %#lx\n", di->got_avma); } else { @@ -2612,6 +2630,13 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) /* Accept .got.plt where mapped as rw (data) */ if (0 == VG_(strcmp)(name, ".got.plt")) { + + if (inrw2) { + inrw = inrw2; + } else { + inrw = inrw1; + } + if (inrw && !di->gotplt_present) { di->gotplt_present = True; di->gotplt_avma = svma + inrw->bias; @@ -2643,9 +2668,9 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) # elif defined(VGP_ppc32_linux) /* Accept .plt where mapped as rw (data) */ if (0 == VG_(strcmp)(name, ".plt")) { - if (inrw && !di->plt_present) { + if (inrw1 && !di->plt_present) { di->plt_present = True; - di->plt_avma = svma + inrw->bias; + di->plt_avma = svma + inrw1->bias; di->plt_size = size; TRACE_SYMTAB("acquiring .plt avma = %#lx\n", di->plt_avma); } else { @@ -2655,13 +2680,13 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) # elif defined(VGP_ppc64be_linux) || defined(VGP_ppc64le_linux) /* Accept .plt where mapped as rw (data), or unmapped */ if (0 == VG_(strcmp)(name, ".plt")) { - if (inrw && !di->plt_present) { + if (inrw1 && !di->plt_present) { di->plt_present = True; - di->plt_avma = svma + inrw->bias; + di->plt_avma = svma + inrw1->bias; di->plt_size = size; TRACE_SYMTAB("acquiring .plt avma = %#lx\n", di->plt_avma); } else - if ((!inrw) && (!inrx) && size > 0 && !di->plt_present) { + if ((!inrw1) && (!inrx) && size > 0 && !di->plt_present) { /* File contains a .plt, but it didn't get mapped. Presumably it is not required on this platform. At least don't reject the situation as invalid. */ @@ -2678,9 +2703,9 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) /* Accept .opd where mapped as rw (data) */ if (0 == VG_(strcmp)(name, ".opd")) { - if (inrw && !di->opd_present) { + if (inrw1 && !di->opd_present) { di->opd_present = True; - di->opd_avma = svma + inrw->bias; + di->opd_avma = svma + inrw1->bias; di->opd_size = size; TRACE_SYMTAB("acquiring .opd avma = %#lx\n", di->opd_avma); } else { @@ -2700,8 +2725,8 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) di->ehframe_avma[di->n_ehframe]); di->n_ehframe++; } else - if (inrw && di->n_ehframe < N_EHFRAME_SECTS) { - di->ehframe_avma[di->n_ehframe] = svma + inrw->bias; + if (inrw1 && di->n_ehframe < N_EHFRAME_SECTS) { + di->ehframe_avma[di->n_ehframe] = svma + inrw1->bias; di->ehframe_size[di->n_ehframe] = size; TRACE_SYMTAB("acquiring .eh_frame avma = %#lx\n", di->ehframe_avma[di->n_ehframe]); @@ -3606,6 +3631,94 @@ Bool ML_(read_elf_debug_info) ( struct _DebugInfo* di ) /* NOTREACHED */ } +Bool ML_(check_elf_and_get_rw_loads) ( Int fd, const HChar* filename, Int * rw_load_count ) +{ + Bool res, ok; + UWord i; + DiImage* mimg = NULL; + DiOffT ehdr_mioff = 0; + DiOffT phdr_mioff = 0; + UWord phdr_mnent = 0U; + UWord phdr_ment_szB = 0U; + + res = False; + + mimg = ML_(img_from_fd)(fd, filename); + if (mimg == NULL) { + VG_(message)(Vg_UserMsg, "warning: connection to image %s failed\n", + filename ); + VG_(message)(Vg_UserMsg, " cannot read program headers \n" ); + return False; + } + + ok = is_elf_object_file_by_DiImage(mimg, False); + if (!ok) { + goto out; + } + + ElfXX_Ehdr ehdr_m; + Elf64_Word flag_x; +#if defined(VGA_amd64) || defined(VGA_ppc64be) || defined(VGA_ppc64le) || defined(VGA_arm) || defined(VGA_arm64) + flag_x = PF_X; +#else + flag_x = 0; +#endif + vg_assert(ehdr_mioff == 0); // ensured by its initialisation + ok = ML_(img_valid)(mimg, ehdr_mioff, sizeof(ehdr_m)); + vg_assert(ok); // ML_(is_elf_object_file) should ensure this + ML_(img_get)(&ehdr_m, mimg, ehdr_mioff, sizeof(ehdr_m)); + + phdr_mioff = ehdr_mioff + ehdr_m.e_phoff; + phdr_mnent = ehdr_m.e_phnum; + phdr_ment_szB = ehdr_m.e_phentsize; + + for (i = 0U; i < phdr_mnent; i++) { + ElfXX_Phdr a_phdr; + ML_(img_get)(&a_phdr, mimg, + INDEX_BIS(phdr_mioff, i, phdr_ment_szB), + sizeof(a_phdr)); + + if (a_phdr.p_type == PT_LOAD) { + if (a_phdr.p_memsz > 0) { + if (((a_phdr.p_flags & (PF_R | PF_W)) == (PF_R | PF_W)) && + ((a_phdr.p_flags & flag_x) == 0)) { + ++*rw_load_count; + } + + /* + * Hold your horses + * Just because The ELF file contains 2 RW PT_LOAD segments it + * doesn't mean that Valgrind will also make 2 calls to + * VG_(di_notify_mmap). If the stars are all aligned + * (which usually means that the ELF file is the client + * executable with the segment offset for the + * second PT_LOAD falls exactly on 0x1000) then the NSegements + * will get merged and VG_(di_notify_mmap) only gets called once. */ + if (*rw_load_count == 2 && + ehdr_m.e_type == ET_EXEC && + a_phdr.p_offset == VG_PGROUNDDN(a_phdr.p_offset) ) + { + *rw_load_count = 1; + } + } + } + } /* for (i = 0; i < phdr_Mnent; i++) ... */ + + res = True; + + out: + { + /* Last, but not least, detach from the image(s). */ + if (mimg) ML_(img_free)(mimg); + + return res; + } /* out: */ + + /* NOTREACHED */ +} + + + #endif // defined(VGO_linux) || defined(VGO_solaris) || defined(VGO_freebsd) /*--------------------------------------------------------------------*/ diff --git a/coregrind/m_debuginfo/readpdb.c b/coregrind/m_debuginfo/readpdb.c index a53cf48c44..f3a3817d89 100644 --- a/coregrind/m_debuginfo/readpdb.c +++ b/coregrind/m_debuginfo/readpdb.c @@ -2363,7 +2363,7 @@ Bool ML_(read_pdb_debug_info)( map.rx = False; map.rw = True; VG_(addToXA)(di->fsm.maps, &map); - di->fsm.have_rw_map = True; + di->fsm.rw_map_count = 1; di->data_present = True; if (di->data_avma == 0) { @@ -2385,7 +2385,7 @@ Bool ML_(read_pdb_debug_info)( } } - if (di->fsm.have_rx_map && di->fsm.have_rw_map && !di->have_dinfo) { + if (di->fsm.have_rx_map && di->fsm.rw_map_count && !di->have_dinfo) { vg_assert(di->fsm.filename); TRACE_SYMTAB("\n"); TRACE_SYMTAB("------ start PE OBJECT with PDB INFO " diff --git a/coregrind/m_debuginfo/storage.c b/coregrind/m_debuginfo/storage.c index 9ba74076c1..6eb932747f 100644 --- a/coregrind/m_debuginfo/storage.c +++ b/coregrind/m_debuginfo/storage.c @@ -616,7 +616,7 @@ void ML_(addLineInfo) ( struct _DebugInfo* di, /* Rule out ones which are completely outside the r-x mapped area. See "Comment_Regarding_Text_Range_Checks" elsewhere in this file for background and rationale. */ - vg_assert(di->fsm.have_rx_map && di->fsm.have_rw_map); + vg_assert(di->fsm.have_rx_map && di->fsm.rw_map_count); if (ML_(find_rx_mapping)(di, this, this + size - 1) == NULL) { if (0) VG_(message)(Vg_DebugMsg, @@ -798,7 +798,7 @@ void ML_(addDiCfSI) ( struct _DebugInfo* di, "warning: DiCfSI %#lx .. %#lx is huge; length = %u (%s)\n", base, base + len - 1, len, di->soname); - vg_assert(di->fsm.have_rx_map && di->fsm.have_rw_map); + vg_assert(di->fsm.have_rx_map && di->fsm.rw_map_count); /* Find mapping where at least one end of the CFSI falls into. */ map = ML_(find_rx_mapping)(di, base, base); map2 = ML_(find_rx_mapping)(di, base + len - 1, @@ -1304,7 +1304,7 @@ void ML_(addVar)( struct _DebugInfo* di, /* This is assured us by top level steering logic in debuginfo.c, and it is re-checked at the start of ML_(read_elf_debug_info). */ - vg_assert(di->fsm.have_rx_map && di->fsm.have_rw_map); + vg_assert(di->fsm.have_rx_map && di->fsm.rw_map_count); if (level > 0 && ML_(find_rx_mapping)(di, aMin, aMax) == NULL) { if (VG_(clo_verbosity) > 1) { VG_(message)(Vg_DebugMsg, diff --git a/memcheck/tests/Makefile.am b/memcheck/tests/Makefile.am index 474ef5c4c9..c8fa5b5cbf 100644 --- a/memcheck/tests/Makefile.am +++ b/memcheck/tests/Makefile.am @@ -342,6 +342,7 @@ EXTRA_DIST = \ varinfo4.stderr.exp-freebsd \ varinfo5.vgtest varinfo5.stdout.exp varinfo5.stderr.exp \ varinfo5.stderr.exp-ppc64 \ + varinfo5.stderr.exp-freebsd \ varinfo6.vgtest varinfo6.stdout.exp varinfo6.stderr.exp \ varinfo6.stderr.exp-ppc64 \ varinforestrict.vgtest varinforestrict.stderr.exp \ diff --git a/memcheck/tests/varinfo5.stderr.exp-freebsd b/memcheck/tests/varinfo5.stderr.exp-freebsd new file mode 100644 index 0000000000..df30c00d40 --- /dev/null +++ b/memcheck/tests/varinfo5.stderr.exp-freebsd @@ -0,0 +1,191 @@ +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: varinfo1_main (tests/varinfo5so.c:52) + by 0x........: varinfo5_main (tests/varinfo5so.c:154) + by 0x........: main (tests/varinfo5.c:5) + Address 0x........ is 1 bytes inside a block of size 3 alloc'd + at 0x........: malloc (vg_replace_malloc.c:...) + by 0x........: varinfo1_main (tests/varinfo5so.c:50) + by 0x........: varinfo5_main (tests/varinfo5so.c:154) + by 0x........: main (tests/varinfo5.c:5) + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: varinfo1_main (tests/varinfo5so.c:55) + by 0x........: varinfo5_main (tests/varinfo5so.c:154) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 0 bytes inside global var "global_u1" + declared at varinfo5so.c:38 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: varinfo1_main (tests/varinfo5so.c:56) + by 0x........: varinfo5_main (tests/varinfo5so.c:154) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 0 bytes inside global var "global_i1" + declared at varinfo5so.c:40 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: varinfo1_main (tests/varinfo5so.c:57) + by 0x........: varinfo5_main (tests/varinfo5so.c:154) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 0 bytes inside global_u2[3], + a global variable declared at varinfo5so.c:42 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: varinfo1_main (tests/varinfo5so.c:58) + by 0x........: varinfo5_main (tests/varinfo5so.c:154) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 0 bytes inside global_i2[7], + a global variable declared at varinfo5so.c:44 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: varinfo1_main (tests/varinfo5so.c:59) + by 0x........: varinfo5_main (tests/varinfo5so.c:154) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 0 bytes inside local var "local" + declared at varinfo5so.c:49, in frame #X of thread 1 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: foo2 (tests/varinfo5so.c:71) + by 0x........: varinfo2_main (tests/varinfo5so.c:81) + by 0x........: varinfo5_main (tests/varinfo5so.c:155) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 0 bytes inside var[7], + declared at varinfo5so.c:69, in frame #X of thread 1 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: foo2 (tests/varinfo5so.c:73) + by 0x........: varinfo2_main (tests/varinfo5so.c:81) + by 0x........: varinfo5_main (tests/varinfo5so.c:155) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 2 bytes inside var.bar, + declared at varinfo5so.c:72, in frame #X of thread 1 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: foo2 (tests/varinfo5so.c:76) + by 0x........: varinfo2_main (tests/varinfo5so.c:81) + by 0x........: varinfo5_main (tests/varinfo5so.c:155) + by 0x........: main (tests/varinfo5.c:5) + Address 0x........ is on thread 1's stack + in frame #X, created by foo2 (varinfo5so.c:66) + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: foo3 (tests/varinfo5so.c:106) + by 0x........: varinfo3_main (tests/varinfo5so.c:118) + by 0x........: varinfo5_main (tests/varinfo5so.c:156) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 0 bytes inside static_global_def[1], + a global variable declared at varinfo5so.c:87 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: foo3 (tests/varinfo5so.c:107) + by 0x........: varinfo3_main (tests/varinfo5so.c:118) + by 0x........: varinfo5_main (tests/varinfo5so.c:156) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 0 bytes inside nonstatic_global_def[2], + a global variable declared at varinfo5so.c:88 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: foo3 (tests/varinfo5so.c:108) + by 0x........: varinfo3_main (tests/varinfo5so.c:118) + by 0x........: varinfo5_main (tests/varinfo5so.c:156) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 0 bytes inside static_global_undef[3], + a global variable declared at varinfo5so.c:89 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: foo3 (tests/varinfo5so.c:109) + by 0x........: varinfo3_main (tests/varinfo5so.c:118) + by 0x........: varinfo5_main (tests/varinfo5so.c:156) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 0 bytes inside nonstatic_global_undef[4], + a global variable declared at varinfo5so.c:90 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: bar3 (tests/varinfo5so.c:94) + by 0x........: foo3 (tests/varinfo5so.c:110) + by 0x........: varinfo3_main (tests/varinfo5so.c:118) + by 0x........: varinfo5_main (tests/varinfo5so.c:156) + by 0x........: main (tests/varinfo5.c:5) + Address 0x........ is 5 bytes inside data symbol "foo3.static_local_def" + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: bar3 (tests/varinfo5so.c:95) + by 0x........: foo3 (tests/varinfo5so.c:110) + by 0x........: varinfo3_main (tests/varinfo5so.c:118) + by 0x........: varinfo5_main (tests/varinfo5so.c:156) + by 0x........: main (tests/varinfo5.c:5) + Address 0x........ is on thread 1's stack + in frame #X, created by foo3 (varinfo5so.c:101) + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: bar3 (tests/varinfo5so.c:96) + by 0x........: foo3 (tests/varinfo5so.c:110) + by 0x........: varinfo3_main (tests/varinfo5so.c:118) + by 0x........: varinfo5_main (tests/varinfo5so.c:156) + by 0x........: main (tests/varinfo5.c:5) + Address 0x........ is 7 bytes inside data symbol "foo3.static_local_undef" + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: bar3 (tests/varinfo5so.c:97) + by 0x........: foo3 (tests/varinfo5so.c:110) + by 0x........: varinfo3_main (tests/varinfo5so.c:118) + by 0x........: varinfo5_main (tests/varinfo5so.c:156) + by 0x........: main (tests/varinfo5.c:5) + Address 0x........ is on thread 1's stack + in frame #X, created by foo3 (varinfo5so.c:101) + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: blah4 (tests/varinfo5so.c:137) + by 0x........: varinfo4_main (tests/varinfo5so.c:146) + by 0x........: varinfo5_main (tests/varinfo5so.c:157) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 1 byte inside a[3].xyzzy[21].c1, + declared at varinfo5so.c:135, in frame #X of thread 1 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: blah4 (tests/varinfo5so.c:138) + by 0x........: varinfo4_main (tests/varinfo5so.c:146) + by 0x........: varinfo5_main (tests/varinfo5so.c:157) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 0 bytes inside a[5].bong, + declared at varinfo5so.c:135, in frame #X of thread 1 + +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: blah4 (tests/varinfo5so.c:139) + by 0x........: varinfo4_main (tests/varinfo5so.c:146) + by 0x........: varinfo5_main (tests/varinfo5so.c:157) + by 0x........: main (tests/varinfo5.c:5) + Location 0x........ is 1 byte inside a[3].xyzzy[21].c2[2], + declared at varinfo5so.c:135, in frame #X of thread 1 + +answer is 0 +Uninitialised byte(s) found during client check request + at 0x........: croak (tests/varinfo5so.c:29) + by 0x........: fun_c (tests/varinfo5so.c:164) + by 0x........: fun_b (./varinfo5so.c:168) + by 0x........: fun_a (./varinfo5so.c:172) + by 0x........: inlinetest (./varinfo5so.c:178) + by 0x........: varinfo5_main (tests/varinfo5so.c:158) + by 0x........: main (tests/varinfo5.c:5) + Address 0x........ is on thread 1's stack + in frame #X, created by inlinetest (varinfo5so.c:176) +