-
Notifications
You must be signed in to change notification settings - Fork 15.7k
Description
I ran into an issue running BOLT on a program that accesses a global struct field provided by the linker. When accessing a field of that struct, the optimized binary wipes the addend and instead replaces reads of that field with reads of the struct+0.
Versions of things
I tested with both x86 and aarch64 and managed to reproduce.
$ llvm-bolt --version
LLVM (http://llvm.org/):
LLVM version 19.0.0git
Optimized build with assertions.
BOLT revision bb6a4850553dd4140a5bd63187ec1b14d0b731f9$ gcc --version
gcc (GCC) 7.3.1 20180712 (Red Hat 7.3.1-17)Repro
This is a minimal repro on x86.
The following is the C, print_global.c
#include <stdio.h>
#include <stdint.h>
struct build_id_note {
char pad[16];
uint8_t hash[20];
};
extern const struct build_id_note build_id_note;
void print_build_id()
{
int x;
for (x = 0; x < 20; x++) {
printf("%02hhx", build_id_note.hash[x]);
}
printf("\n");
}
int main() {
print_build_id();
return 0;
}I used the default linker script outputted by the -Wl,-verbose gcc flag, then inserted the build_id_note at an absolute location right under that first PROVIDE in build_id.ld, like so:
/* first bit of default linker script removed */
SECTIONS
{
/* Read-only sections, merged into text segment: */
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
.note.gnu.build-id (0x400200):
{
build_id_note = ABSOLUTE(.);
*(.note.gnu.build-id)
}
/* rest of default linker script removed */
}This is compiled with
gcc -o print_global print_global.c -Wl,-T,build_id.ld -Wl,--emit-relocs -Wl,--build-id=sha1Then bolting:
$ llvm-bolt ./print_global -o print_global.bolted --funcs print_build_id
BOLT-INFO: Target architecture: x86_64
BOLT-INFO: BOLT version: bb6a4850553dd4140a5bd63187ec1b14d0b731f9
BOLT-INFO: first alloc address is 0x200000
BOLT-INFO: creating new program header table at address 0x800000, offset 0x600000
BOLT-INFO: enabling relocation mode
BOLT-INFO: enabling -align-macro-fusion=all since no profile was specified
BOLT-INFO: enabling lite mode
BOLT-INFO: 0 out of 12 functions in the binary (0.0%) have non-empty execution profile
BOLT-INFO: setting _end to 0xa001cc
BOLT-INFO: patched build-id (flipped last bit)The output of the non-bolted binary is:
$ ./print_global
74007820799e8dc7eb6ff742fe419c5866842b5bYet, the bolted binary produces the wrong result
$ ./print_global.bolted
040000001400000003000000474e550074007820
Of course, the build id is still as expected for the bolted binary:
$ readelf -n print_global.bolted
Displaying notes found in: .note.gnu.build-id
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 74007820799e8dc7eb6ff742fe419c5866842b5aSo this is a case where BOLT leads to incorrect runtime behavior. Inspecting the assembly, we see the root of the problem is that the immediate address for build_id_note.hash is missing the addend and thus we print starting from the pad field:
Non-bolted asm:
$ gdb -batch -ex "disassemble print_build_id" print_global
Dump of assembler code for function print_build_id:
0x00000000004004e7 <+0>: push %rbp
0x00000000004004e8 <+1>: mov %rsp,%rbp
0x00000000004004eb <+4>: sub $0x10,%rsp
0x00000000004004ef <+8>: movl $0x0,-0x4(%rbp)
0x00000000004004f6 <+15>: jmp 0x40051c <print_build_id+53>
0x00000000004004f8 <+17>: mov -0x4(%rbp),%eax
0x00000000004004fb <+20>: cltq
0x00000000004004fd <+22>: movzbl 0x400210(%rax),%eax <---------- Access of build_id_note.hash
0x0000000000400504 <+29>: movzbl %al,%eax
0x0000000000400507 <+32>: mov %eax,%esi
0x0000000000400509 <+34>: mov $0x4005d0,%edi
0x000000000040050e <+39>: mov $0x0,%eax
0x0000000000400513 <+44>: callq 0x400410 <printf@plt>
0x0000000000400518 <+49>: addl $0x1,-0x4(%rbp)
0x000000000040051c <+53>: cmpl $0x13,-0x4(%rbp)
0x0000000000400520 <+57>: jle 0x4004f8 <print_build_id+17>
0x0000000000400522 <+59>: mov $0xa,%edi
0x0000000000400527 <+64>: callq 0x400400 <putchar@plt>
0x000000000040052c <+69>: nop
0x000000000040052d <+70>: leaveq
0x000000000040052e <+71>: retq
End of assembler dump.
Bolted asm:
$ gdb -batch -ex "disassemble print_build_id" print_global.bolted
Dump of assembler code for function print_build_id:
0x0000000000a00000 <+0>: push %rbp
0x0000000000a00001 <+1>: mov %rsp,%rbp
0x0000000000a00004 <+4>: sub $0x10,%rsp
0x0000000000a00008 <+8>: movl $0x0,-0x4(%rbp)
0x0000000000a0000f <+15>: jmp 0xa00035 <print_build_id+53>
0x0000000000a00011 <+17>: mov -0x4(%rbp),%eax
0x0000000000a00014 <+20>: cltq
0x0000000000a00016 <+22>: movzbl 0x400200(%rax),%eax <---------- Uh oh, should be 0x400210
0x0000000000a0001d <+29>: movzbl %al,%eax
0x0000000000a00020 <+32>: mov %eax,%esi
0x0000000000a00022 <+34>: mov $0x4005d0,%edi
0x0000000000a00027 <+39>: mov $0x0,%eax
0x0000000000a0002c <+44>: callq 0x400410 <printf@plt>
0x0000000000a00031 <+49>: addl $0x1,-0x4(%rbp)
0x0000000000a00035 <+53>: cmpl $0x13,-0x4(%rbp)
0x0000000000a00039 <+57>: jle 0xa00011 <print_build_id+17>
0x0000000000a0003b <+59>: mov $0xa,%edi
0x0000000000a00040 <+64>: callq 0x400400 <putchar@plt>
0x0000000000a00045 <+69>: leaveq
0x0000000000a00046 <+70>: retq
End of assembler dump.