Отслеживание функций вызывающего и вызываемого только с 16 регистрами в x86-64 - PullRequest
0 голосов
/ 14 октября 2018

Я запутался в этих сохраненных звонках и сохраненных звонках регистрах.Что если функция является как вызывающей, так и вызываемой?

Скажем, функция main вызывает функцию P, а функция P вызывает функцию Q.В этом случае, если P является одновременно вызываемым (для main) и вызывающим (для Q), то какой регистр будет использовать сборка?

1 Ответ

0 голосов
/ 14 октября 2018

Для простоты я возьму Система V ABI и ограничу это объяснение целочисленными параметрами и указателями .Для более подробного описания соглашений о вызовах для Unix см. этот пост .


Первые шесть аргументов передаются функции в регистрах rdi, rsi, rdx, rcx, r8 и r9.Эти регистры сохраняются вызывающим абонентом , т. Е. Они могут не сохраняться между вызовами функций, так как они вызываемый могут их засорять (более подходящим термином для них будет call-Clobbered регистров).

Давайте объявим Q как:

int Q(int, int, int, int, int, int);

То есть Q принимает шесть целых чисел, так что сгенерированный компилятором код вынужден использовать всеэти шесть регистров, сохраненных вызывающими.

Теперь давайте определим P следующим образом:

int P(int a, int b, int c, int d, int e, int f) {  
    return Q(a, b, c, d, e, f) + Q(f, e, d, c, b, a);
}

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

Приведенный выше код генерирует следующую сборку:

P:
  // save rbx, rbp, r12, r13, r14 and r15 onto the stack
  // copy rdi, rsi, rdx, rcx, r8 and r9 into those registers
  pushq %r15
  movl %esi, %r15d
  pushq %r14
  movl %edx, %r14d
  pushq %r13
  movl %ecx, %r13d
  pushq %r12
  movl %r8d, %r12d
  pushq %rbp
  movl %r9d, %ebp
  pushq %rbx
  movl %edi, %ebx
  subq $24, %rsp
  call Q // <-- 1st call to Q (may clobber rdi, rsi, rdx, rcx, r8 and r9)

  // prepare rdi, rsi, rdx, rcx, r8 and r9 for the 2nd call to Q
  movl %ebx, %r9d
  movl %r15d, %r8d
  movl %r14d, %ecx
  movl %r13d, %edx
  movl %r12d, %esi
  movl %ebp, %edi
  movl %eax, 12(%rsp)
  call Q // <-- 2nd call to Q
  addl 12(%rsp), %eax
  addq $24, %rsp
  popq %rbx
  popq %rbp
  popq %r12
  popq %r13
  popq %r14
  popq %r15
  ret

Вызов Qразрешено блокировать регистры rdi, rsi, rdx, rcx, r8 и r9 (среди прочих).Эти регистры содержат аргументы вызова P (то есть a, b, c, d, e и f соответственно), и их значения все еще необходимы для второгопозвоните по номеру Q.По этой причине регистры rbx, rbp, r12, r13, r14 и r15 используются для копирования этих регистров перед вызовом Q, чтобы сохранить исходные аргументы P был вызван с.

Q не разрешено блокировать эти регистры, которые используются для сохранения регистров, сохраненных вызываемым абонентом, поскольку это регистры сохраненные вызывающим абонентом , т.е. их значениеДолжен быть сохранен между вызовами функций (другой термин для них будет сохраняемый вызов регистры).Поэтому код в функции P сохраняет эти регистры в стек с помощью push перед тем, как их забить, и восстанавливает их с помощью pop из стека, прежде чем поток управления вернется из P (P - это абонент из Q здесь).Чистый эффект, который увидит вызывающий P, состоит в том, что эти регистры имеют свое первоначальное значение, то же значение, которое они имели непосредственно перед вызовом P.

Обратите внимание, что второй вызов Q может заглушить регистры rdi, rsi, rdx, rcx, r8 и r9, но нет никакого интереса в сохранении их значений, так как они больше не сохраняются после Qвозвращает.


Для более глубокого понимания сбоев регистров посмотрите Какие регистры сохраняются посредством вызова функции linux x86-64 .

...