[PATCH] bfd/ELF: Handle multiple build-ids in corefile
Jan Beulich
jbeulich@suse.com
Thu Nov 27 08:30:45 GMT 2025
On 26.11.2025 16:58, Tom de Vries wrote:
> Consider the following test-case:
> ...
> $ cat abort.c
> int main (void) { __builtin_abort (); }
> $ gcc abort.c
> $ ./a.out
> Aborted (core dumped)
> $
> ...
>
> If we use gdb to load both the exec and corefile:
> ...
> $ gdb -q -batch a.out core
> [New LWP 2568650]
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/lib64/libthread_db.so.1".
> Core was generated by `./a.out'.
> Program terminated with signal SIGABRT, Aborted.
> 44 return INTERNAL_SYSCALL_ERROR_P (ret) ? INTERNAL_SYSCALL_ERRNO (ret) : 0;
> $
> ...
> then bfd_elf64_core_file_matches_executable_p is called, which returns true
> because core_bfd->build_id->data == exec_bfd->build_id.
>
> Likewise if we rename a.out to b.out:
> ...
> $ mv a.out b.out
> $ gdb -q -batch b.out core
>
> warning: Can't open file /data/vries/gdb/a.out during file-backed mapping \
> note processing
> [New LWP 2568650]
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/lib64/libthread_db.so.1".
> Core was generated by `./a.out'.
> Program terminated with signal SIGABRT, Aborted.
> 44 return INTERNAL_SYSCALL_ERROR_P (ret) ? INTERNAL_SYSCALL_ERRNO (ret) : 0;
> ...
>
> The only difference is that we get a warning from gdb about not being able to
> find a.out.
>
> Sofar, this is on openSUSE Leap 16.0 x86_64. Let's try the same again in an
> openSUSE Leap 15.6 podman container on the same system.
>
> After recompiling and generating the core file, we have:
> ...
> $ gdb -q -batch a.out core
> [New LWP 3176707]
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/lib64/libthread_db.so.1".
> Core was generated by `./a.out'.
> Program terminated with signal SIGABRT, Aborted.
> #0 $hex in __pthread_kill_implementation () from /lib64/libc.so.6
> $
> ...
>
> Again, bfd_elf64_core_file_matches_executable_p is called, but
> core_bfd->build_id->data != exec_bfd->build_id, so it falls back to the
> filename comparison, which matches so still the function returns true.
>
> But after renaming a.out:
> ...
> $ mv a.out b.out
> $ gdb -q -batch b.out core
>
> warning: core file may not match specified executable file.
> [New LWP 3176707]
> [Thread debugging using libthread_db enabled]
> Using host libthread_db library "/lib64/libthread_db.so.1".
> Core was generated by `./a.out'.
> Program terminated with signal SIGABRT, Aborted.
> #0 $hex in __pthread_kill_implementation () from /lib64/libc.so.6
> $
> ...
> bfd_elf64_core_file_matches_executable_p returns false, and gdb issues the
> "may not match" warning.
>
> So in the latter setup, why do we have
> core_bfd->build_id->data != exec_bfd->build_id?
>
> The sizes are the same:
> ...
> (gdb) p core_bfd->build_id.size
> $1 = 20
> (gdb) p exec_bfd->build_id.size
> $2 = 20
> ...
>
> But the data doesn't match:
> ...
> (gdb) p /x *(char[20] *)&core_bfd->build_id.data
> $3 = {0x23, 0x4d, 0x18, 0x74, 0xdf, 0x7c, 0x0, 0x12, 0xfc, 0x40, 0xa8, 0x8b, \
> 0x6f, 0xde, 0xc0, 0xb2, 0x1e, 0xa0, 0x58, 0xe2}
> (gdb) p /x *(char[20] *)&exec_bfd->build_id.data
> $4 = {0x69, 0x3f, 0xcf, 0x4f, 0xb, 0x41, 0x36, 0x4c, 0xe2, 0x55, 0xc4, 0x6e, \
> 0xc9, 0xd8, 0x41, 0x81, 0x71, 0xab, 0xd3, 0xb4}
> ...
>
> The exec_bfd->build_id.data is correct:
> ...
> $ file b.out
> b.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically \
> linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 4.3.0, \
> BuildID[sha1]=693fcf4f0b41364ce255c46ec9d8418171abd3b4, with debug_info, \
> not stripped
> ...
>
> But the core_bfd->build_id.data matches one of the shared libs:
> ...
> $ ldd b.out
> linux-vdso.so.1 (0x00007f99578c3000)
> libc.so.6 => /lib64/libc.so.6 (0x00007f99576a1000)
> /lib64/ld-linux-x86-64.so.2 (0x00007f99578c5000)
> $ file /lib64/ld-linux-x86-64.so.2
> /lib64/ld-linux-x86-64.so.2: ELF 64-bit LSB shared object, x86-64, version 1 \
> (GNU/Linux), dynamically linked, \
> BuildID[sha1]=234d1874df7c0012fc40a88b6fdec0b21ea058e2, not stripped
> ...
>
> The problem is the logic used while reading the core file, which considers that
> the build id of a core file (meaning, the build id of the executable from which
> the corefile was generated) is the first one encountered.
>
> Usually, that is the case, but in this case not.
But why is this so? Of course we can assume that build IDs will never
collide, but it's a simple mathematical fact that with enough different
binaries some will have the same hash. IOW ...
> Fix this by:
> - changing the type struct bfd_build_id to include a next field, turning it
> into a list,
> - while reading the core, storing all build ids encountered in that list
> - in elf_core_file_matches_executable_p, checking the list of build ids.
... rather than making a list, determining what the executable's build ID
is would seem to be the way to go. If that's ambiguous in the core file,
then perhaps the writing of the core file is what would need adjustment.
> --- a/bfd/elfcore.h
> +++ b/bfd/elfcore.h
> @@ -50,12 +50,14 @@ elf_core_file_matches_executable_p (bfd *core_bfd, bfd *exec_bfd)
> }
>
> /* If both BFDs have identical build-ids, then they match. */
> - if (core_bfd->build_id != NULL
> - && exec_bfd->build_id != NULL
> - && core_bfd->build_id->size == exec_bfd->build_id->size
> - && memcmp (core_bfd->build_id->data, exec_bfd->build_id->data,
> - core_bfd->build_id->size) == 0)
> - return true;
> + if (core_bfd->build_id != NULL)
> + {
> + const struct bfd_build_id *idx;
> + for (idx = core_bfd->build_id; idx != NULL; idx = idx->next)
> + if (idx->size == exec_bfd->build_id->size
> + && memcmp (idx->data, exec_bfd->build_id->data, idx->size) == 0)
> + return true;
> + }
Nit: No real need for the outer if(), afaict.
Jan
More information about the Binutils
mailing list