Я столкнулся с некоторыми проблемами в понимании записей таблиц перемещений, скомпилированных из исходных файлов C. Мои программы следующие:
//a.c
extern int shared;
int main(){
int a = 100;
swap(&a, &shared);
a = 200;
shared = 1;
swap(&a, &shared);
}
//b.c
int shared = 1;
void swap(int* a, int* b) {
if (a != b)
*b ^= *a ^= *b, *a ^= *b;
}
Я компилирую и связываю их с помощью следующих команд gcc -c -fno-stack-protector a.c b.c
и ld a.o b.o -e main -o ab
.
Затем я objdump -r a.o
, чтобы проверить его таблицу перемещений.
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000014 R_X86_64_32 shared
0000000000000021 R_X86_64_PC32 swap-0x0000000000000004
000000000000002e R_X86_64_PC32 shared-0x0000000000000008
000000000000003b R_X86_64_32 shared
0000000000000048 R_X86_64_PC32 swap-0x0000000000000004
Разборка a.o
есть
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: c7 45 fc 64 00 00 00 movl $0x64,-0x4(%rbp)
f: 48 8d 45 fc lea -0x4(%rbp),%rax
13: be 00 00 00 00 mov $0x0,%esi
18: 48 89 c7 mov %rax,%rdi
1b: b8 00 00 00 00 mov $0x0,%eax
20: e8 00 00 00 00 callq 25 <main+0x25>
25: c7 45 fc c8 00 00 00 movl $0xc8,-0x4(%rbp)
2c: c7 05 00 00 00 00 01 movl $0x1,0x0(%rip) # 36 <main+0x36>
33: 00 00 00
36: 48 8d 45 fc lea -0x4(%rbp),%rax
3a: be 00 00 00 00 mov $0x0,%esi
3f: 48 89 c7 mov %rax,%rdi
42: b8 00 00 00 00 mov $0x0,%eax
47: e8 00 00 00 00 callq 4c <main+0x4c>
4c: b8 00 00 00 00 mov $0x0,%eax
51: c9 leaveq
52: c3 retq
У меня такой вопрос:
shared
на позиции 14 и shared
на позиции 2e - это абсолютно одни и те же объекты. Почему у них разные названия символов?
Это тот же адрес, но типы переселения разные. Типы перемещения определены в x86-64-abi.
В чем разница?
В 0x14
и 0x3b
: адрес глобальной переменной shared
должен быть перемещен в регистр %rsi
, чтобы вызвать функцию swap
.
Однако, поскольку программа была скомпилирована с помощью -mcmodel=small
(по умолчанию для gcc, см. Также этот вопрос), компилятор может предположить, что адрес подходит к 32-битному и использует movl
вместо movq
(в противном случае компилятор использовал бы другие инструкции, но сравнивая movl
с "наивный" movq
довольно хорошо объясняет разницу), для которого потребуется больше байтов для кодирования.
Таким образом, результирующим перемещением будет R_X86_64_32
(т.е. 64-битный адрес, усеченный до 32-битного без расширения знака), а не R_X86_64_64
, то есть компоновщик запишет 4 младших байта адреса вместо заполнителя, который также имеет ширину 4 байта.
В 0x2e
вы хотите записать значение 1
по адресу памяти shared
. Однако целевой адрес указывается относительно %rip
, то есть относительно 0x36
:
movl $0x1,0x0(%rip) # 36 <main+0x36>
Очевидно, что простая установка абсолютного адреса shared
через R_X86_64_32
не принесет никакой пользы - необходимы более сложные вычисления, и для этого нужен R_X86_64_PC32
.
Еще раз, из-за небольшой модели кода компилятор может предположить, что 32-битного относительного смещения копирования достаточно (и, таким образом, используется перемещение R_X86_64_PC32
, а не R_X86_64_PC64
), а ширина заполнителя составляет всего 4 байта.
Взято из x86-64-abi, формула перемещения выглядит так (раздел 4.4):
result = S+A-P (32bit-word, i.e. the lower 4 bytes of the result)
S = the value of the symbol whose index resides in the relocation entry
A = the addend used to compute the value of the relocatable field
P = the place (section offset or address) of the storage unit being relocated (computed using r_offset)
Это означает:
S
- это адрес переменной shared
.A
- это -8
(можно увидеть, например, вызвав readelf -r a.o
или objdump -r a.o
), потому что разница в 8 байтов между смещением 0x2e
перемещения и фактическим %rip
- 0x36
.P
- это смещение релокации, т.е. 0x26
. P-A
- это адрес в %rip
.Как видите, в результате получается не S
, как в случае с R_X86_64_32
выше, а S - (P-A)
. Это также можно увидеть в результирующем двоичном файле - разные значения будут исправлены в заполнителях для этих двух разных типов перемещения.
Там - отличная статья на эту тему от Эли Бендерски.