x86-64 порядок передачи параметров в регистрах - PullRequest
0 голосов
/ 07 сентября 2018

Мне интересно узнать о процедуре передачи параметров в среде x86-64, и поэтому я написал фрагмент кода.

//a.c
extern int shared;
int main(){
    int a=100;
    swap(&a, &shared);
}
//b.c
int shared=1;
void swap(int* a, int* b){
    *a ^= *b ^= *a ^= *b;
}

Я компилирую два файла, используя следующие команды: gcc -c -fno-stack-protector a.c b.c Тогда я objdump -d a.o, чтобы проверить код разборки .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:   b8 00 00 00 00          mov    $0x0,%eax
  2a:   c9                      leaveq 
  2b:   c3                      retq

Из-за моей рабочей среды Ubuntu 16.04 x86-64 мне было трудно понять порядок передачи параметра.

На мой взгляд, соглашение о вызовах по умолчанию здесь fastcall, и поэтому параметры передаются из справа налево .

Я знаю из руководства x86-64 System V ABI, что rdi и rsi используются для передачи первых двух параметров enter image description here

Однако, согласно коду разборки, rdi отвечает за var a, который является параметром слева, то есть это должен быть второй параметр.

Может ли кто-нибудь помочь мне указать на мою ошибку?

Ответы [ 2 ]

0 голосов
/ 07 сентября 2018

Ваша идея первого / второго неверна. Отсчет начинается слева.

Кроме того, у вашего кода есть некоторые проблемы, по крайней мере:

  1. Вы вызываете swap без объявления / прототипа, поэтому GCC решил генерировать код, который работал бы, если бы это была функция с переменным числом (сохранение 0 в %rax). .

  2. Определение swap - это куча неопределенного поведения.

0 голосов
/ 07 сентября 2018

Аргументы пронумерованы слева направо (спасибо @R. За то, что вы заметили, что это действительно ваше замешательство; я думал, что вы говорили о порядке инструкций asm, и пропустил последний абзац вопрос.)

Выглядит нормально для меня. Когда выполняется инструкция call swap,

  • rdi содержит указатель на a (локальный в стеке), установленный
    lea -0x4(%rbp),%rax и mov %rax,%rdi.

    (вместо lea в rdi, потому что вы не включили оптимизацию.)

  • rsi содержит указатель на shared, настроенный на mov $shared,%esi
  • al содержит 0, потому что вы не определили или не создали прототип функции перед ее вызовом. (GCC должен был предупредить вас об этом даже без -Wall)

Демонтаж .o показывает $shared как 0, потому что он еще не связан, так что это заполнитель (и смещение 0) от символа. Используйте objdump -drwC, чтобы увидеть символы перемещения. (Мне также нравится -Mintel вместо синтаксиса AT & T.)

Также проще посмотреть на вывод asm компилятора, где вместо числа и ссылки на символ вы увидите $shared. См. Как удалить «шум» из вывода сборки GCC / clang? .


Неважно, какие регистры порядка записываются, только их значение при входе в вызываемую функцию. То же самое для аргументов стека: если компилятор решит использовать mov для записи аргументов в стек, он может сделать это в любом порядке.

Только если вы решите использовать push, вам нужно идти справа налево, чтобы оставить первый (самый левый) аргумент по самому низкому адресу, как того требуют все основные соглашения о вызовах C для аргументов, которые не ' t передано в регистрах (если есть).

Этот порядок справа налево может быть причиной, по которой gcc -O0 (без оптимизации, плюс антиоптимизация для отладки) выбирает регистры в этом порядке, даже если это не имеет значения.


Кстати, xor-swap бесполезен и бессмыслен, даже если он реализован правильно без UB. ( Существуют ли точки последовательности в выражении a ^ = b ^ = a ^ = b или оно не определено? ).

if(a==b) { *a = *b = 0; } else { int tmp = *a; *a=*b; *b=tmp; } - более эффективный своп, который сохраняет поведение безопасного xor-swap обнуления, если оба указателя относятся к одному и тому же объекту. Я полагаю, вы хотите этого? Зачем еще использовать xor-swap?

Генерируемый компилятором asm для любого из них будет в основном плохим, если вы не включите оптимизацию, как код для main отстой по той же причине. И если вы это сделаете, swap часто может быть встроенным и содержать ноль инструкций, или просто стоит до 3 mov инструкций между регистрами; иногда меньше. (Компилятор может просто изменить распределение регистров и решить, что a и b теперь находятся в противоположных регистрах.)

...