Это тот же адрес, но типы перемещения разные.Типы перемещения определены в 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)
.Это также можно увидеть в полученном двоичном файле - в заполнителях для этих двух разных типов перемещения будут исправлены разные значения.
Там есть отличная статья на эту тему от EliБендерский.