Qual é o objetivo da instrução LEA?


676

Para mim, parece um MOV descolado. Qual é o seu propósito e quando devo usá-lo?


2
Consulte também Usando LEA em valores que não são endereços / ponteiros? : LEA é apenas uma instrução shift-and-add. Provavelmente foi adicionado ao 8086 porque o hardware já está lá para decodificar e calcular os modos de endereçamento, não porque é "destinado" apenas para uso com endereços. Lembre-se de que ponteiros são apenas números inteiros na montagem.
Peter Cordes

Respostas:


798

Como outros já apontaram, o LEA (carregamento efetivo do endereço) é frequentemente usado como um "truque" para realizar certos cálculos, mas esse não é seu objetivo principal. O conjunto de instruções x86 foi projetado para suportar linguagens de alto nível, como Pascal e C, onde matrizes - especialmente matrizes de entradas ou pequenas estruturas - são comuns. Considere, por exemplo, uma estrutura representando (x, y) coordenadas:

struct Point
{
     int xcoord;
     int ycoord;
};

Agora imagine uma declaração como:

int y = points[i].ycoord;

Onde points[]é uma matriz de Point. Assumindo que a base da matriz já está em EBX, e variável ié em EAX, e xcoorde ycoordsão cada 32 bits (por isso ycoordé no deslocamento 4 bytes na estrutura), esta afirmação pode ser compilado para:

MOV EDX, [EBX + 8*EAX + 4]    ; right side is "effective address"

que vai pousar yno EDX. O fator de escala 8 é porque cada um Pointtem 8 bytes de tamanho. Agora considere a mesma expressão usada com o operador "address of" &:

int *p = &points[i].ycoord;

Nesse caso, você não deseja o valor de ycoord, mas o endereço. É aí que LEAentra (o endereço efetivo da carga). Em vez de a MOV, o compilador pode gerar

LEA ESI, [EBX + 8*EAX + 4]

que carregará o endereço ESI.


112
Não teria sido mais limpo estender as movinstruções e deixar os parênteses? MOV EDX, EBX + 8*EAX + 4
Natan Yellin

14
@imacake Ao substituir o LEA por um MOV especializado, você mantém a sintaxe limpa: [] colchetes sempre equivalem a desreferenciar um ponteiro em C. Sem colchetes, você sempre lida com o ponteiro.
Natan Yellin

139
Fazer matemática em uma instrução MOV (EBX + 8 * EAX + 4) não é válido. LEA ESI, [EBX + 8 * EAX + 4] é válido porque este é um modo de endereçamento suportado pelo x86. en.wikipedia.org/wiki/X86#Addressing_modes
Erik

29
@JonathanDickinson LEA é como um MOVcom uma fonte indireta, exceto que apenas faz o indireto e não o MOV. Na verdade, ele não o endereço calculado, apenas calcula.
Hbbs 28/08/2013

24
Erik, o comentário da turnê não é exato. MOV eax, [ebx + 8 * ecx + 4] é válido. No entanto MOV retorna o conteúdo de thst local de memória enquanto LEA retorna o endereço
Olorin

562

Do "Zen da Assembléia" de Abrash:

LEA, a única instrução que executa cálculos de endereçamento de memória, mas na verdade não endereça a memória. LEAaceita um operando de endereçamento de memória padrão, mas não faz nada além de armazenar o deslocamento de memória calculado no registro especificado, que pode ser qualquer registro de uso geral.

O que isso nos dá? Duas coisas que ADDnão fornecem:

  1. a capacidade de realizar adição com dois ou três operandos, e
  2. a capacidade de armazenar o resultado em qualquer registro; não apenas um dos operandos de origem.

E LEAnão altera as bandeiras.

Exemplos

  • LEA EAX, [ EAX + EBX + 1234567 ]calcula EAX + EBX + 1234567(são três operandos)
  • LEA EAX, [ EBX + ECX ]calcula EBX + ECXsem substituir o resultado.
  • multiplicação por constante (por dois, três, cinco ou nove), se você usá-lo como LEA EAX, [ EBX + N * EBX ](N pode ser 1,2,4,8).

Outro caso de uso é útil em loops: a diferença entre LEA EAX, [ EAX + 1 ]e INC EAXé que o último muda, EFLAGSmas o primeiro não; isso preserva o CMPestado.


42
@AbidRahmanK alguns exemplos: LEA EAX, [ EAX + EBX + 1234567 ]calcula a soma de EAX, EBXe 1234567(são três operandos). LEA EAX, [ EBX + ECX ]calcula EBX + ECX sem substituir o resultado. A terceira coisa a LEAser usada (não listada por Frank) é a multiplicação por constante (por duas, três, cinco ou nove), se você usá-la como LEA EAX, [ EBX + N * EBX ]( Npode ser 1,2,4,8). Outro caso de uso é útil em loops: a diferença entre LEA EAX, [ EAX + 1 ]e INC EAXé que o último muda, EFLAGSmas o primeiro não; isso preserva o CMPestado
FrankH.

@FrankH. Eu ainda não entendo, então carrega um ponteiro em outro lugar?

6
@ ripDaddy69 sim, mais ou menos - se por "carga" você quer dizer "executa a aritmética de cálculo de endereço / ponteiro". Ele não acessa a memória (ou seja, não desrefere o ponteiro como seria chamado em termos de programação C).
FrankH.

2
+1: Isso torna explícito para que tipos de 'truques' LEApodem ser usados ​​... (consulte "LEA (endereço efetivo de carga) é frequentemente usado como um" truque "para realizar certos cálculos" na resposta popular de IJ Kennedy acima)
Assad Ebrahim

3
Há uma grande diferença entre o 2 operando LEA, que é rápido e 3 operando, que é lento. O manual Intel Optimization diz que o LEA de caminho rápido é de ciclo único e o LEA de caminho lento leva três ciclos. Além disso, no Skylake, existem duas unidades funcionais de caminho rápido (portas 1 e 5) e há apenas uma unidade funcional de caminho lento (porta 1). A Regra 33 de Codificação de Montagem / Compilador no manual até adverte contra o uso de 3 operandos LEA.
Olsonist

110

Outra característica importante da LEAinstrução é que ela não altera os códigos de condição como CFe ZF, enquanto calcula o endereço por instruções aritméticas como ADDou MULfaz. Esse recurso diminui o nível de dependência entre as instruções e, portanto, abre espaço para otimização adicional pelo compilador ou planejador de hardware.


1
Sim, leaàs vezes é útil para o compilador (ou codificador humano) fazer matemática sem prejudicar um resultado de flag. Mas leanão é mais rápido que add. A maioria das instruções x86 grava sinalizadores. As implementações de alto desempenho x86 precisam renomear EFLAGS ou evitar o risco de gravação após gravação para que o código normal seja executado rapidamente; portanto, as instruções que evitam gravações de sinalizadores não são melhores por causa disso. ( Parcial coisas bandeira pode criar problemas, consulte a instrução INC vs ADD 1: Será que isso importa? )
Peter Cordes

2
@ PeterCordes: Odeio trazer isso aqui, mas - estou sozinho pensando que essa nova tag [x86-lea] é redundante e desnecessária?
precisa saber é o seguinte

2
@ MichaelPetch: Sim, acho que é muito específico. Parece confundir iniciantes que não entendem a linguagem de máquina e que tudo (incluindo ponteiros) são apenas bits / bytes / números inteiros; portanto, há muitas perguntas sobre isso com um grande número de votos. Mas ter uma etiqueta implica que há espaço para um número ilimitado de perguntas futuras, quando na verdade existem cerca de 2 ou 3 no total que não são apenas duplicatas. (o que é? Como usá-lo para multiplicar números inteiros e como ele é executado internamente no UAG vs. ALUs e com que latência / débito e talvez seja "destinada" propósito?.)
Peter Cordes

@ PeterCordes: eu concordo, e se alguma coisa todas essas postagens sendo editadas forem praticamente uma duplicata de algumas das questões relacionadas à LEA que estão saindo. Em vez de uma tag, todas as duplicatas devem ser identificadas e marcadas como imho.
22818 Michael Petch

1
@EvanCarroll: aguarde a marcação de todas as perguntas da LEA, se você ainda não tiver concluído. Como discutido acima, consideramos o x86-lea muito específico para uma tag e não há muito espaço para futuras perguntas não duplicadas. Eu acho que seria muito trabalho realmente escolher uma "melhor" sessão de perguntas e respostas como alvo de dup para a maioria deles, ou decidir realmente quais os mods a serem mesclados.
Peter Cordes

93

Apesar de todas as explicações, o LEA é uma operação aritmética:

LEA Rt, [Rs1+a*Rs2+b] =>  Rt = Rs1 + a*Rs2 + b

Só que o nome é extremamente estúpido para uma operação shift + add. A razão para isso já foi explicada nas respostas com melhor classificação (ou seja, foi projetada para mapear diretamente as referências de memória de alto nível).


8
E que a aritmética é realizada pelo hardware de cálculo de endereço.
Ben Voigt

30
@BenVoigt Eu costumava dizer isso, porque eu sou um cara velho :-) Tradicionalmente, as CPUs x86 usavam as unidades de endereçamento para isso, concordou. Mas a "separação" ficou muito embaçada nos dias de hoje. Algumas CPUs não têm mais AGUs dedicadas , outras optaram por não executar LEAnas AGUs, mas nas ALUs inteiras comuns. É preciso ler as especificações da CPU muito de perto hoje em dia para descobrir "onde as coisas correm" ...
FrankH.

2
@FrankH .: CPUs fora de ordem normalmente executam LEA em ALUs, enquanto algumas CPUs em ordem (como Atom) às vezes o executam em AGUs (porque não podem estar ocupadas lidando com um acesso à memória).
27568 Peter Panes

3
Não, o nome não é estúpido. LEAfornece o endereço que surge de qualquer modo de endereçamento relacionado à memória. Não é uma operação de troca e adição.
Kaz

3
FWIW, existem muito poucas (se houver) CPUs x86 atuais que executam a operação na AGU. A maioria ou todos usam apenas uma ALU como qualquer outra operação aritmética.
BeeOnRope

77

Talvez apenas outra coisa sobre a instrução LEA. Você também pode usar o LEA para registros de multiplicação rápida por 3, 5 ou 9.

LEA EAX, [EAX * 2 + EAX]   ;EAX = EAX * 3
LEA EAX, [EAX * 4 + EAX]   ;EAX = EAX * 5
LEA EAX, [EAX * 8 + EAX]   ;EAX = EAX * 9

13
+1 para o truque. Mas eu gostaria de fazer uma pergunta (pode ser estúpido), por que não multiplicar diretamente com três assim LEA EAX, [EAX*3]?
Abid Rahman K

13
@Abid Rahman K: Não há instruções como o conjunto de instruções da CPU x86.
GJ.

50
@AbidRahmanK, apesar da sintaxe intel asm parecer uma multiplicação, a instrução lea pode codificar apenas operações de turno. O opcode tem 2 bits para descrever a mudança, portanto, você pode multiplicar apenas por 1,2,4 ou 8. #
970

6
@Koray Tugay: Você pode usar a shlinstrução shift left left para multiplicar registros por 2,4,8,16 ... é mais rápido e mais curto. Mas, para multiplicar com números diferentes de potência 2, normalmente usamos mulinstruções mais pretensiosas e lentas.
GJ.

7
@GJ. embora não exista essa codificação, alguns montadores aceitam isso como um atalho, por exemplo, fasm. Então, por exemplo, lea eax,[eax*3]seria traduzido para equivalente a lea eax,[eax+eax*2].
Ruslan

59

leaé uma abreviação de "carregar endereço efetivo". Carrega o endereço da referência de localização pelo operando de origem no operando de destino. Por exemplo, você pode usá-lo para:

lea ebx, [ebx+eax*8]

para mover ainda mais os itens do ebxponteiro eax(em uma matriz de 64 bits / elemento) com uma única instrução. Basicamente, você se beneficia dos modos complexos de endereçamento suportados pela arquitetura x86 para manipular os ponteiros com eficiência.


23

O maior motivo que você usa LEAsobre a MOVé se você precisa executar aritmética nos registros que está usando para calcular o endereço. Efetivamente, você pode executar o que equivale a aritmética de ponteiro em vários registros em combinação de forma eficaz para "grátis".

O que é realmente confuso é que você normalmente escreve um exemplo LEAcomo um, MOVmas na verdade não está desreferenciando a memória. Em outras palavras:

MOV EAX, [ESP+4]

Isso moverá o conteúdo do que ESP+4aponta para EAX.

LEA EAX, [EBX*8]

Isso moverá o endereço efetivo EBX * 8para o EAX, não o que é encontrado nesse local. Como você pode ver, também é possível multiplicar por fatores de dois (dimensionamento) enquanto a MOVé limitado a adicionar / subtrair.


Desculpe todo mundo. O @ big.heart me enganou ao dar uma resposta a isso há três horas, fazendo com que ele aparecesse como "novo" na minha pergunta da Assembléia.
David Hoelzer 6/15

1
Por que a sintaxe usa colchetes quando não faz endereçamento de memória?
golopot

3
@ q4w56 Essa é uma das coisas em que a resposta é: "É assim que você faz". Eu acredito que é uma das razões pelas quais as pessoas têm dificuldade em descobrir o que LEAfaz.
David Hoelzer

2
@ q4w56: é uma instrução shift + add que usa sintaxe de operando de memória e codificação de código de máquina. Em algumas CPUs, pode até usar o hardware AGU, mas esse é um detalhe histórico. O fato ainda relevante é que o hardware do decodificador já existe para decodificar esse tipo de shift + add, e o LEA nos permite usá-lo para aritmética em vez de endereçamento de memória. (Ou para cálculos de endereço, se uma entrada é realmente um ponteiro).
Peter Cordes

20

O 8086 possui uma grande família de instruções que aceitam um operando de registro e um endereço efetivo, realizam alguns cálculos para calcular a parte deslocada desse endereço efetivo e realizam algumas operações envolvendo o registro e a memória referida pelo endereço calculado. Era bastante simples ter uma das instruções dessa família como acima, exceto para pular essa operação de memória real. Isto, as instruções:

mov ax,[bx+si+5]
lea ax,[bx+si+5]

foram implementados quase de forma idêntica internamente. A diferença é uma etapa ignorada. Ambas as instruções funcionam algo como:

temp = fetched immediate operand (5)
temp += bx
temp += si
address_out = temp  (skipped for LEA)
trigger 16-bit read  (skipped for LEA)
temp = data_in  (skipped for LEA)
ax = temp

Quanto à razão pela qual a Intel pensou que essa instrução valia a pena incluir, não tenho muita certeza, mas o fato de ser barato de implementar teria sido um grande fator. Outro fator teria sido o fato de o montador da Intel permitir a definição de símbolos em relação ao registro BP. Se fnordfoi definido como um símbolo relativo à BP (por exemplo, BP + 8), pode-se dizer:

mov ax,fnord  ; Equivalent to "mov ax,[BP+8]"

Se alguém quiser usar algo como stosw para armazenar dados em um endereço relativo à BP, poderá dizer

mov ax,0 ; Data to store
mov cx,16 ; Number of words
lea di,fnord
rep movs fnord  ; Address is ignored EXCEPT to note that it's an SS-relative word ptr

foi mais conveniente do que:

mov ax,0 ; Data to store
mov cx,16 ; Number of words
mov di,bp
add di,offset fnord (i.e. 8)
rep movs fnord  ; Address is ignored EXCEPT to note that it's an SS-relative word ptr

Observe que esquecer o mundo "offset" faria com que o conteúdo da localização [BP + 8], em vez do valor 8, fosse adicionado ao DI. Opa


12

Como as respostas existentes mencionadas, LEAtem as vantagens de executar aritmética de endereçamento de memória sem acessar a memória, salvando o resultado aritmético em um registro diferente em vez da forma simples de instrução add. O benefício real de desempenho subjacente é que o processador moderno possui uma unidade e porta LEA ALU separadas para geração eficaz de endereços (incluindo LEAe outro endereço de referência de memória), isso significa que a operação aritmética LEAe outra operação aritmética normal na ALU podem ser feitas em paralelo em um testemunho.

Consulte este artigo da arquitetura Haswell para obter mais detalhes sobre a unidade LEA: http://www.realworldtech.com/haswell-cpu/4/

Outro ponto importante que não é mencionado em outras respostas é a LEA REG, [MemoryAddress]instrução é o PIC (código independente de posição) que codifica o endereço relativo do PC nesta instrução para referência MemoryAddress. É diferente do MOV REG, MemoryAddressque codifica o endereço virtual relativo e requer a realocação / aplicação de patches nos sistemas operacionais modernos (como o ASLR é um recurso comum). Portanto, LEApode ser usado para converter esses não PIC em PIC.


2
A parte "LEA ALU" separada é basicamente falsa. As CPUs modernas são executadas leaem uma ou mais das mesmas ALUs que executam outras instruções aritméticas (mas geralmente menos do que outras aritméticas). Por exemplo, a CPU Haswell mencionada pode executar addou a submaioria das outras operações aritméticas básicas em quatro ALUs diferentes , mas pode executar apenas leaem uma (complexa lea) ou duas (simples lea). Mais importante ainda, essas duas leaALUs com capacidade são simplesmente duas das quatro que podem executar outras instruções, portanto, não há benefício de paralelismo conforme reivindicado.
BeeOnRope

O artigo que você vinculou (corretamente) mostra que o LEA está na mesma porta que uma ALU inteira (add / sub / boolean) e a unidade MUL inteira em Haswell. (E ALUs vetoriais, incluindo FP ADD / MUL / FMA). A unidade LEA simples está na porta 5, que também executa ADD / SUB / qualquer que seja, embaralha vector e outras coisas. A única razão pela qual não estou com voto negativo é que você aponta o uso do LEA relativo ao RIP (apenas para x86-64).
Peter Cordes

8

A instrução LEA pode ser usada para evitar cálculos demorados de endereços efetivos pela CPU. Se um endereço for usado repetidamente, é mais eficaz armazená-lo em um registro em vez de calcular o endereço efetivo toda vez que for usado.


Não necessariamente no x86 moderno. A maioria dos modos de endereçamento tem o mesmo custo, com algumas ressalvas. Portanto, [esi]raramente é mais barato do que dizer [esi + 4200]e raramente é mais barato do que [esi + ecx*8 + 4200].
BeeOnRope 26/06

@BeeOnRope [esi]não é mais barato que [esi + ecx*8 + 4200]. Mas por que se preocupar em comparar? Eles não são equivalentes. Se você deseja que o primeiro designe o mesmo local de memória que o último, você precisa de instruções adicionais: você deve adicionar ao esivalor ecxmultiplicado por 8. Uh, a multiplicação vai derrubar os sinalizadores da CPU! É necessário adicionar o 4200. Essas instruções adicionais aumentam o tamanho do código (ocupando espaço no cache de instruções, ciclos para buscar).
Kaz

2
@ Kaz - Eu acho que você estava perdendo o meu ponto (ou então eu perdi o ponto do OP). Meu entendimento é que o OP está dizendo que, se você usar algo como [esi + 4200]repetidamente em uma sequência de instruções, é melhor primeiro carregar o endereço efetivo em um registro e usá-lo. Por exemplo, em vez de escrever add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200], você deve preferir lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi], o que raramente é mais rápido. Pelo menos essa é a interpretação clara dessa resposta.
BeeOnRope

Portanto, o motivo pelo qual eu estava comparando [esi]e [esi + 4200](ou [esi + ecx*8 + 4200]é que essa é a simplificação que o OP está propondo (como eu a entendo)): que N instruções com o mesmo endereço complexo são transformadas em N instruções com endereçamento simples (um registro), mais um lea, desde endereçamento complexo é "demorado" na verdade, é mais lento, mesmo em x86 moderna, mas apenas latência-wise que parece improvável que importa para obter instruções consecutivos com o mesmo endereço..
BeeOnRope

1
Talvez você alivie alguma pressão do registro, sim - mas o oposto pode ser o caso: se os registros com os quais você gerou o endereço efetivo estiverem ativos, você precisará de outro registro para salvar o resultado lea, aumentando a pressão nesse caso. Em geral, o armazenamento de intermediários é uma causa da pressão do registro, não uma solução para isso - mas acho que na maioria das situações é uma lavagem. @Kaz
BeeOnRope

7

A instrução LEA (Load Effective Address) é uma maneira de obter o endereço que surge em qualquer um dos modos de endereçamento de memória do processador Intel.

Ou seja, se tivermos dados movidos assim:

MOV EAX, <MEM-OPERAND>

move o conteúdo da localização da memória designada para o registro de destino.

Se substituirmos MOVpor LEA, o endereço da localização da memória será calculado exatamente da mesma maneira pela <MEM-OPERAND>expressão de endereçamento. Mas, em vez do conteúdo da localização da memória, obtemos a própria localização no destino.

LEAnão é uma instrução aritmética específica; é uma maneira de interceptar o endereço efetivo resultante de qualquer um dos modos de endereçamento de memória do processador.

Por exemplo, podemos usar LEAapenas um endereço direto simples. Nenhuma aritmética está envolvida:

MOV EAX, GLOBALVAR   ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR   ; fetch the address of GLOBALVAR into EAX.

Isso é válido; podemos testá-lo no prompt do Linux:

$ as
LEA 0, %eax
$ objdump -d a.out

a.out:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <.text>:
   0:   8d 04 25 00 00 00 00    lea    0x0,%eax

Aqui, não há adição de um valor escalado nem deslocamento. Zero é movido para o EAX. Poderíamos fazer isso usando MOV com um operando imediato também.

Essa é a razão pela qual as pessoas que pensam que os colchetes LEAsão supérfluos estão seriamente enganadas; os colchetes não são LEAsintaxe, mas fazem parte do modo de endereçamento.

O LEA é real no nível do hardware. A instrução gerada codifica o modo de endereçamento real e o processador o executa até o ponto de calcular o endereço. Em seguida, move esse endereço para o destino em vez de gerar uma referência de memória. (Como o cálculo do endereço de um modo de endereçamento em qualquer outra instrução não afeta os sinalizadores da CPU, LEAnão afeta os sinalizadores da CPU).

Contraste com o carregamento do valor do endereço zero:

$ as
movl 0, %eax
$ objdump -d a.out | grep mov
   0:   8b 04 25 00 00 00 00    mov    0x0,%eax

É uma codificação muito semelhante, entende? Apenas o 8dde LEAmudou para 8b.

Obviamente, essa LEAcodificação é mais longa do que mover um zero imediato para EAX:

$ as
movl $0, %eax
$ objdump -d a.out | grep mov
   0:   b8 00 00 00 00          mov    $0x0,%eax

Não há razão para LEAexcluir essa possibilidade, apenas porque existe uma alternativa mais curta; está apenas combinando de forma ortogonal com os modos de endereçamento disponíveis.


6

Aqui está um exemplo.

// compute parity of permutation from lexicographic index
int parity (int p)
{
  assert (p >= 0);
  int r = p, k = 1, d = 2;
  while (p >= k) {
    p /= d;
    d += (k << 2) + 6; // only one lea instruction
    k += 2;
    r ^= p;
  }
  return r & 1;
}

Com -O (otimizar) como opção de compilador, o gcc encontrará a instrução lea para a linha de código indicada.


6

Parece que muitas respostas já estão completas. Gostaria de adicionar mais um código de exemplo para mostrar como as instruções lea e move funcionam de maneira diferente quando elas têm o mesmo formato de expressão.

Para encurtar a história, as instruções lea e as instruções mov podem ser usadas com os parênteses que encerram o operando src das instruções. Quando eles são colocados com o () , a expressão no () é calculada da mesma maneira; no entanto, duas instruções interpretarão o valor calculado no operando src de uma maneira diferente.

Se a expressão é usada com lea ou mov, o valor src é calculado como abaixo.

D (Rb, Ri, S) => (Reg [Rb] + S * Reg [Ri] + D)

No entanto, quando é usada com a instrução mov, ela tenta acessar o valor apontado pelo endereço gerado pela expressão acima e armazená-lo no destino.

Por outro lado, quando a instrução lea é executada com a expressão acima, ela carrega o valor gerado como está no destino.

O código abaixo executa a instrução lea e a instrução mov com o mesmo parâmetro. No entanto, para entender a diferença, adicionei um manipulador de sinal no nível do usuário para detectar a falha de segmentação causada pelo acesso a um endereço errado como resultado da instrução mov.

Código de exemplo

#define _GNU_SOURCE 1  /* To pick up REG_RIP */
#include <stdio.h> 
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>


uint32_t
register_handler (uint32_t event, void (*handler)(int, siginfo_t*, void*))
{
        uint32_t ret = 0;
        struct sigaction act;

        memset(&act, 0, sizeof(act));
        act.sa_sigaction = handler;
        act.sa_flags = SA_SIGINFO;
        ret = sigaction(event, &act, NULL);
        return ret;
}

void
segfault_handler (int signum, siginfo_t *info, void *priv)
{
        ucontext_t *context = (ucontext_t *)(priv);
        uint64_t rip = (uint64_t)(context->uc_mcontext.gregs[REG_RIP]);
        uint64_t faulty_addr = (uint64_t)(info->si_addr);

        printf("inst at 0x%lx tries to access memory at %ld, but failed\n",
                rip,faulty_addr);
        exit(1);
}

int
main(void)
{
        int result_of_lea = 0;

        register_handler(SIGSEGV, segfault_handler);

        //initialize registers %eax = 1, %ebx = 2

        // the compiler will emit something like
           // mov $1, %eax
           // mov $2, %ebx
        // because of the input operands
        asm("lea 4(%%rbx, %%rax, 8), %%edx \t\n"
            :"=d" (result_of_lea)   // output in EDX
            : "a"(1), "b"(2)        // inputs in EAX and EBX
            : // no clobbers
         );

        //lea 4(rbx, rax, 8),%edx == lea (rbx + 8*rax + 4),%edx == lea(14),%edx
        printf("Result of lea instruction: %d\n", result_of_lea);

        asm volatile ("mov 4(%%rbx, %%rax, 8), %%edx"
                       :
                       : "a"(1), "b"(2)
                       : "edx"  // if it didn't segfault, it would write EDX
          );
}

Resultado de execução

Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed

1
Dividir seu asm inline em declarações separadas não é seguro, e suas listas de rejeitos estão incompletas. O bloco basic-asm informa que o compilador não possui clobbers, mas na verdade modifica vários registros. Além disso, você pode =ddizer ao compilador que o resultado está no EDX, salvando um mov. Você também deixou de fora uma declaração antecipada na saída. Isso demonstra o que você está tentando demonstrar, mas também é um péssimo exemplo enganoso de asm inline que será interrompido se usado em outros contextos. Isso é uma coisa ruim para uma resposta de estouro de pilha.
Peter Cordes

Se você não quiser escrever %%sobre todos esses nomes de registro no Extended asm, use restrições de entrada. gosto asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));. Permitir que o init do compilador seja registrado significa que você também não precisa declarar clobbers. Você está supercomplicando as coisas com xor-zerando antes que mov-imediato substitua o registro inteiro também.
Peter Cordes

@PeterCordes Obrigado, Peter, você quer que eu exclua esta resposta ou modifique-a após seus comentários?
Jaehyuk Lee

1
Se você corrigir o asm inline, ele não fará nenhum mal e talvez seja um bom exemplo concreto para iniciantes que não entenderam as outras respostas. Não há necessidade de excluir, e é uma solução fácil, como mostrei no meu último comentário. Eu acho que valeria a pena uma votação se o mau exemplo de inline asm fosse corrigido em um exemplo "bom". (Eu não downvote)
Peter Cordes

1
Onde alguém diz que isso mov 4(%ebx, %eax, 8), %edxé inválido? De qualquer forma, sim, movpois faria sentido escrever "a"(1ULL)para informar ao compilador que você tem um valor de 64 bits e, portanto, ele precisa garantir que ele seja estendido para preencher todo o registro. Na prática, ele ainda será usado mov $1, %eax, porque escrever EAX zero se estende ao RAX, a menos que você tenha uma situação estranha de código circundante em que o compilador sabia que RAX = 0xff00000001ou algo assim. Pois leavocê ainda está usando o tamanho de operando de 32 bits, para que quaisquer bits altos e dispersos nos registros de entrada não tenham efeito no resultado de 32 bits.
Peter Cordes

4

LEA: apenas uma instrução "aritmética" ..

MOV transfere dados entre operandos, mas lea está apenas calculando


O LEA obviamente move os dados; tem um operando de destino. O LEA nem sempre calcula; calcula se o endereço efetivo expresso no operando de origem calcula. LEA EAX, GLOBALVAR não calcula; apenas move o endereço de GLOBALVAR para EAX.
Kaz

@Kaz obrigado pelo seu feedback. minha fonte era "LEA (endereço efetivo de carga) é essencialmente uma instrução aritmética - ele não realiza nenhum acesso real à memória, mas é comumente usado para calcular endereços (embora você possa calcular inteiros de uso geral com ele)". formulário Eldad-Eilam livro página 149
o contador

@Kaz: É por isso que o LEA é redundante quando o endereço já é uma constante no tempo do link; use em mov eax, offset GLOBALVARvez disso. Você pode usar o LEA, mas é um tamanho de código um pouco maior que mov r32, imm32e é executado em menos portas, porque ainda passa pelo processo de cálculo de endereço . lea reg, symbolé útil apenas em 64 bits para um LEA relativo ao RIP, quando você precisa de PIC e / ou endereços fora dos 32 bits baixos. No código de 32 ou 16 bits, não há vantagem nenhuma. LEA é uma instrução aritmética que expõe a capacidade da CPU de decodificar / calcular os modos de endereçamento.
Peter Cordes

@ Kaz: pelo mesmo argumento, você poderia dizer que imul eax, edx, 1não calcula: apenas copia edx para eax. Mas, na verdade, ele executa seus dados através do multiplicador com latência de 3 ciclos. Ou rorx eax, edx, 0apenas copia (gire em zero).
Peter Cordes

@PeterCordes Meu argumento é que tanto o LEA EAX, o GLOBALVAL quanto o MOV EAX, o GLOBALVAR simplesmente pegam o endereço de um operando imediato. Não há multiplicador de 1 ou deslocamento de 0 sendo aplicado; pode ser assim no nível do hardware, mas não é visto na linguagem assembly ou no conjunto de instruções.
Kaz

1

Todas as instruções normais de "cálculo", como adição de multiplicação, exclusividade ou definição dos sinalizadores de status como zero, sinal. Se você usar um endereço complicado, AX xor:= mem[0x333 +BX + 8*CX] os sinalizadores serão definidos de acordo com a operação xor.

Agora você pode querer usar o endereço várias vezes. O carregamento desses endereços em um registro nunca se destina a definir sinalizadores de status e, felizmente, não. A frase "carregar endereço efetivo" informa o programador sobre isso. É daí que vem a expressão estranha.

É claro que, uma vez que o processador é capaz de usar o endereço complicado para processar seu conteúdo, ele é capaz de calculá-lo para outros fins. De fato, pode ser usado para realizar uma transformação x <- 3*x+1em uma instrução. Esta é uma regra geral na programação de montagem: use as instruções, porém isso agita o seu barco. A única coisa que conta é se a transformação específica incorporada pela instrução é útil para você.

Bottom line

MOV, X| T| AX'| R| BX|

e

LEA, AX'| [BX]

têm o mesmo efeito no AX, mas não nos sinalizadores de status. (Esta é uma notação de ciasdis .)


"Esta é uma regra geral na programação de montagem: use as instruções, porém isso agita o seu barco." Eu não entregaria pessoalmente esse conselho, por coisas como call lbl lbl: pop raxtecnicamente "trabalhar" como uma maneira de obter o valor rip, mas você tornará a previsão do ramo muito infeliz. Use as instruções que quiser, mas não se surpreenda se você fizer algo complicado e tem consequências que você fez não prevê
The6P4C

@ The6P4C Essa é uma ressalva útil. No entanto, se não houver alternativa para tornar a previsão de ramificação infeliz, é preciso fazer isso. Há outra regra geral na programação de montagem. Pode haver maneiras alternativas de fazer algo e você deve escolher sabiamente as alternativas. Existem centenas de maneiras de inserir o conteúdo do registro BL no registro AL. Se o restante do RAX não precisar ser preservado, o LEA pode ser uma opção. Não afetar os sinalizadores pode ser uma boa idéia em alguns dos milhares de tipos de processadores x86. Groetjes Albert
Albert van der Horst
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.