ARM: link register e frame pointer

Eu estou tentando entender como o registro de link e o ponteiro de quadro funcionam no ARM. Já estive em alguns sites e queria confirmar minha compreensão.

Suponha que eu tenha o seguinte código:

int foo(void) { // .. bar(); // (A) // .. } int bar(void) { // (B) int b1; // .. // (C) baz(); // (D) } int baz(void) { // (E) int a; int b; // (F) } 

e eu chamo foo (). O registrador de links conteria o endereço do código no ponto (A) e o ponteiro do quadro conteria o endereço no código no ponto (B)? E o ponteiro da pilha poderia estar em algum lugar dentro da barra (), depois que todos os locais foram declarados?

[edit] Adicionado outra chamada de function baz ()

Algumas convenções de chamada de registro dependem da ABI (Application Binary Interface). O FP é requerido no padrão APCS e não no mais recente AAPCS (2003). Para o AAPCS (GCC 5.0+), o FP não precisa ser usado, mas certamente pode ser; as informações de debugging são anotadas com o uso de ponteiro de pilha e quadro para rastrear pilha e desenrolar o código com o AAPCS . Se uma function é static , um compilador realmente não precisa aderir a nenhuma das convenções.

Geralmente, todos os registradores ARM são de propósito geral . O lr (registrador de link, também R14) e pc (contador de programa também R15) são especiais e entesourados no conjunto de instruções. Você está correto que o lr apontaria para. O pc e o lr estão relacionados. Um é “onde você está” e o outro é “onde você estava”. Eles são o aspecto de código de uma function.

Normalmente, temos o sp (ponteiro da pilha, R13) e o fp ( ponteiro do quadro , R11). Esses dois também estão relacionados. Esse layout da Microsoft faz um bom trabalho descrevendo as coisas. A pilha é usada para armazenar dados temporários ou locais em sua function. Quaisquer variables ​​em foo() e bar() , são armazenadas aqui, na pilha ou em registradores disponíveis. O fp mantém o controle das variables ​​de function para function. É uma janela de quadro ou imagem na pilha para essa function. A ABI define um layout desse quadro . Normalmente o lr e outros registradores são salvos aqui nos bastidores pelo compilador, assim como o valor anterior de fp . Isso cria uma lista encadeada de frameworks de pilha e, se você quiser, pode rastreá-la até o main() . A raiz é fp , que aponta para um quadro de pilha (como uma struct ) com uma variável na struct sendo o fp anterior. Você pode ir até a lista até o final, que é normalmente NULL .

Então o sp é onde está a pilha e o fp é onde ficava a pilha, muito parecido com o pc e o lr . Cada antigo lr (registro de link) é armazenado no antigo fp (ponteiro de quadro). O sp e o fp são um aspecto de dados das funções.

Seu ponto B é o pc ativo e sp . O ponto A é, na verdade, o fp e o lr ; a menos que você chame ainda outra function e, em seguida, o compilador pode se preparar para configurar o fp para apontar para os dados em B.

A seguir está um montador do ARM que pode demonstrar como tudo isso funciona. Isso será diferente dependendo de como o compilador otimiza, mas deve dar uma ideia,

 ; Prologue - setup mov ip, sp ; get a copy of sp. stmdb sp!, {fp, ip, lr, pc} ; Save the frame on the stack. See Addendum sub fp, ip, #4 ; Set the new frame pointer. ... ; Maybe other functions called here. 
; Older caller return
lr stored in stack frame. bl baz ... ; Epilogue - return ldm sp, {fp, sp, lr} ; restore stack, frame pointer and old link. ... ; maybe more stuff here. bx lr ; return.

É assim que foo() seria. Se você não chamar bar() , o compilador faz uma otimização de folha e não precisa salvar o quadro ; somente o bx lr é necessário. Muito provavelmente isso talvez porque você está confuso com exemplos da web. Não é sempre o mesmo.

O take-away deve ser,

  1. pc e lr são registros de código relacionados. Um é “Onde você está”, o outro é “Onde você estava”.
  2. sp e fp são registros de dados locais relacionados.
    Uma é “Onde os dados locais são”, a outra é “Onde os últimos dados locais estão”.
  3. O trabalho em conjunto junto com a passagem de parâmetros para criar o mecanismo de function .
  4. É difícil descrever um caso geral porque queremos que os compiladores sejam o mais rápido possível, então eles usam todos os truques que podem.

Esses conceitos são genéricos para todas as CPUs e linguagens compiladas, embora os detalhes possam variar. O uso do registrador de link , ponteiro de quadro faz parte do prólogo de function e do epílogo, e se você entendeu tudo, você sabe como um estouro de pilha funciona em um ARM.

Veja também: convenção de chamada ARM .
Artigo da pilha do MSDN ARM
Visão geral da Universidade de Cambridge APCS
Blog de rastreamento de pilha do ARM
Link Apple ABI

O layout básico do quadro é

  • fp [-0] salvou pc , onde armazenamos esse quadro.
  • fp [-1] salvou lr , o endereço de retorno para esta function.
  • fp [-2] sp anterior, antes dessa function comer pilha.
  • fp [-3] fp anterior, o último quadro de pilha .
  • muitos registradores opcionais …

Uma ABI pode usar outros valores, mas os acima são típicos para a maioria das configurações.

Adendo: Isto não é um erro no assembler; é normal. Uma explicação está na questão dos prólogos gerados pelo ARM .

Intereting Posts