Pergunta

Atualmente estou jogando com ARM montagem no Linux como um exercício de aprendizagem. Eu estou usando 'nu' de montagem, ou seja, não libcrt ou libgcc. Pode apontar ninguém me à informação sobre o estado da pilha-ponteiro e outros registros será no início do programa antes da primeira instrução é chamado? Obviamente pontos pc / r15 _start, eo resto parecem ser inicializado a 0, com duas exceções; sp / r13 aponta para um endereço muito fora do meu programa e pontos R1 para um endereço ligeiramente mais elevado.

Assim, para algumas perguntas sólidos:

  • O que é o valor em R1?
  • É o valor em sp uma pilha legítimo atribuído pelo kernel?
  • Se não, qual é o método preferido de atribuição de uma pilha; usando brk ou alocar seção de uma estática .bss?

Os ponteiros seria apreciada.

Foi útil?

Solução

Aqui está o que eu uso para obter um programa Linux / ARM começou com meu compilador:

/** The initial entry point.
 */
asm(
"       .text\n"
"       .globl  _start\n"
"       .align  2\n"
"_start:\n"
"       sub     lr, lr, lr\n"           // Clear the link register.
"       ldr     r0, [sp]\n"             // Get argc...
"       add     r1, sp, #4\n"           // ... and argv ...
"       add     r2, r1, r0, LSL #2\n"   // ... and compute environ.
"       bl      _estart\n"              // Let's go!
"       b       .\n"                    // Never gets here.
"       .size   _start, .-_start\n"
);

Como você pode ver, eu só pegar o argc, argv, e coisas environ da pilha em [SP].

Um pouco esclarecimento: o ponteiro da pilha aponta para uma área válida na memória do processo. R0, R1, R2 e R3 são os três primeiros parâmetros para a função que está sendo chamado. Eu preenchê-los com argc, argv e environ, respectivamente.

Outras dicas

Uma vez que este é o Linux, você pode ver como ele é implementado pelo kernel.

Os registros parecem ser definido pela chamada para start_thread no final do load_elf_binary (se você estiver usando um sistema Linux moderno, ele vai quase sempre estar usando o formato ELF). Para ARM, os registros parecem ser definido da seguinte forma:

r0 = first word in the stack
r1 = second word in the stack
r2 = third word in the stack
sp = address of the stack
pc = binary entry point
cpsr = endianess, thumb mode, and address limit set as needed

É claro que você tem uma pilha válido. Eu acho que os valores de r0-r2 são lixo, e em vez disso você deve ler tudo, desde a pilha (você verá porque eu acho isso mais tarde). Agora, vamos olhar para o que está na pilha. O que você vai ler a partir da pilha é preenchido por create_elf_tables .

Uma coisa interessante notar aqui é que esta função é independente de arquitetura, de modo que as mesmas coisas (principalmente) será colocado na pilha em cada arquitetura baseados em Linux ELF. A seguir está na pilha, na ordem que iria lê-lo:

  • O número de parâmetros (este é argc em main()).
  • Um apontador para uma cadeia C para cada parâmetro, seguido por um zero (isto é o conteúdo de argv em main(); argv apontaria para o primeiro destes ponteiros).
  • Um apontador para uma cadeia C para cada variável ambiente, seguido por um zero (isto é o conteúdo do terceiro parâmetro envp raramente visto de main(); envp apontaria para o primeiro destes ponteiros).
  • A "vector auxiliar", que é uma sequência de pares (um tipo seguido por um valor), terminada por um par com uma (AT_NULL) zero no primeiro elemento. Este vector auxiliar tem algumas informações interessantes e úteis, que você pode ver (se você estiver usando glibc) executando qualquer programa dinamicamente ligada com a variável de set ambiente LD_SHOW_AUXV para 1 (por exemplo LD_SHOW_AUXV=1 /bin/true). Esta é também onde as coisas podem variar um pouco dependendo da arquitetura.

Uma vez que esta estrutura é a mesma para cada arquitetura, você pode olhar, por exemplo, no desenho da página 54 da SYSV 386 ABI para ter uma idéia melhor de como as coisas se encaixam (note, no entanto, que as constantes de tipo vector auxiliares nesse documento são diferentes do que os usos do Linux, então você deve olhar para o Linux cabeçalhos para eles).

Agora você pode ver porque o conteúdo de r0-r2 são lixo. A primeira palavra na pilha é argc, o segundo é um ponteiro para o nome do programa (argv[0]), eo terceiro, provavelmente, era zero para você, porque você chamou o programa sem argumentos (que seria argv[1]). Eu acho que eles são criados desta forma para o formato binário a.out mais velho, que, como você pode ver em create_aout_tables puts argc, argv e envp na pilha (para que eles iria acabar em r0-r2 na ordem esperada para uma chamada para main()).

Finalmente, por que foi r0 zero para você em vez de um (argc deve ser um, se você chamou o programa sem argumentos)? Estou descobrindo algo profundo na maquinaria syscall substituiu-a com o valor de retorno da chamada de sistema (que seria zero desde o exec sucedeu). Você pode ver na kernel_execve ( que não usaa maquinaria syscall, uma vez que é o que as chamadas do kernel quando quer exec do modo kernel) que deliberadamente substitui r0 com o valor de retorno de do_execve.

Aqui está o uClibc CRT . Parece sugerir que todos os registros são indefinido exceto r0 (que contém um ponteiro de função a ser registrado com atexit()) e sp que contém um endereço de pilha válido.

Assim, o valor que você vê em r1 provavelmente não é algo que você pode confiar.

Alguns dados são colocados na pilha para você.

Eu nunca usei ARM Linux, mas eu sugiro que você quer olhar para a fonte para o libcrt e ver o que eles fazem, ou usar o gdb para etapa em um arquivo executável existente. Você não deve precisar o código-fonte apenas passo através do código de montagem.

Tudo o que você precisa descobrir deve acontecer dentro do primeiro código executado por qualquer executável binário.

Espero que isso ajude.

Tony

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top