Вы находитесь на странице: 1из 15

Este artigo ir introduzir os conceitos de assembler em linha (inline assembler) no Delphi.

O artigo dar uma noo bsica do assunto mas no pretende oferecer, em hiptese alguma, detalhes da programao assembler que, por si s, precisariam de um livro inteiro ou mais... Por que e Quando ================ Se voc der uma olhada no cdigo fonte da RTL e da VCL, voc encontrar declaraes assembler inline em vrios pontos. Por que a Borland optou por escrever partes do cdigo da RTL e da VCL em assembler? A resposta bem simples: para alcanar velocidade na execuo. Ns sabemos que o compilador produz cdigo rpido mas um compilador jamais ser to bom quanto um programador assembler profissional. Agora, se o assembler to bom, por que no foi utilizado em toda a RTL e VCL? A resposta igualmente simples: porque na programao de mais alto nvel, mais fcil codificar, depurar, ler e manter o cdigo, de modo que o sacrifcio em velocidade fica compensado pelas convenincias decorrentes. Isso ajuda a explicar quando o assembler deve ser utilizado. Para ser curto, alm do acesso ao sistema em baixo nvel, o assembler inline deve ser utilizado quando a diferena na velocidade de execuo justifica o trabalho adicional da codificao em assembler. Por exemplo, na unidade Math.pas, h muito assembler, basicamente para chamadas de sistema em baixo nvel (para acesso s funes do coprocessador); em System.pas, SysUtils.pas e Classes.pas h tambm diversos blocos em assembler, desta vez para priorizar velocidade; no estranho j que essas podem ser consideradas as unidades centrais da RTL e VCL. Em geral, procedimentos e funes que tendem a ser chamadas de forma repetida por um programa devem ser altamente otimizadas, mas codificao em assembler deve ser evitada tanto quanto possvel. Se desejamos ganhos em velocidade, antes de optar por assembler devemos otimizar o algoritmo propriamente dito; depois, otimizamos o cdigo Pascal. Se optarmos por assembler, o cdigo Pascal otimizado pode servir como documentao e pode ser utilizado como "cdigo de contigncia" no caso de problemas com a manuteno do cdigo assembler. Os Registradores da CPU ======================= Os registradores da CPU so como variveis predefinidas residindo na CPU e, por vezes, tm tarefas especiais. Eles no tm tipo e podem ser vistos como inteiros de 32 bits com ou sem sinal ou como ponteiros, dependendo da situao. Como esto na prpria CPU, muito mais rpido acessar valores contidos nos registradores do que na memria, fazendo dos registros ideais para fazer cache de valores. Como variveis, os registradores tambm possuem nomes. Os nomes daqueles que usaremos so EAX, EBX, ECX, EDX, ESI, EDI, EBP e ESP. Cada registrador tem uma particularidade que o distingue dos demais: - Para algumas instrues, a CPU foi otimizada para utilizar o registrador EAX (tambm conhecido como acumulador) ou ao menos os opcodes so menores. EAX usado nas multiplicaes e as divises,

intructions de string, instrues de I/O, instrues de ajuste ASCII e decimal, e em algumas instrues especiais (como CDQ, LAHF, SAHF e XLAT). EBX um registrador de uso geral, e usado implicitamente por XLAT. ECX (tambm conhecido como contador) tem emprego especial nas instrues LOOP, de rotao e deslocamento de bits e de manipulao de literais. EDX utilizado para armazenar os 32 bits mais altos do resultado de uma multiplicao ou os os 32 bits mais altos do dividendo e do resto de uma diviso. ESI e EDI (conhecidos como ndice de origem (source index) e ndice de destino ("destination index") respectivamente) so como ponteiros utilizados em instrues envolvendo strings. EBP (conhecido como ponteiro base) normalmente usado para enderear valores na pilha (parmetros e variveis locais). ESP (conhecido como ponteiro da pilha) utilizado para controlar a pilha. alterado automaticamente por instrues como PUSH, POP, CALL e RET.

Os registradores EBX, ESI, EDI, EBP e ESP devem ser preservados, o que significa que antes de us-los, devemos salvar seus valores em algum lugar (normalmente na pilha ou outro registradores) e, quando terminarmos de us-los, devemos restaurar seus valores originais (essas operaes implicam no uso de instrues e perda de algum tempo) de modo que o uso desses registradores ser feito somente quando justificvel ou quando houver uma necessidade inevitvel. Provavelmente voc percebeu que os nomes dos registradores iniciam com a letra "E". O "E" representa "Extended", estendido. Nos tempos do Intel 80286, os registradores tinham 16 bits e eram chamados AX, BX, CX, etc. Esses registradores ainda existem e so exatamente os 16 bits menos significativos dos registradores EAX, EBX, ECX, etc., respectivamente. A propsito disso, os registradores AX, BX, CX e DX so divididos em dois registradores de 8 bits. AL, BL, CL e DL so os bytes menos significativos de AX, BX, CX e DX respectivamente, enquanto AH, BH, CH e DH so os bytes mais significativos de AX, BX, CX e DX respectivamente. Por exemplo, se o valor de EAX $7AFD503C, ento o valor de AX $503C, o valor de AH $50 e o valor de AL $3C: 7A FD 50 3C AH AL /----/ AX /------------/ EAX Se, na situao acima, armazenarmos o valor $99 em AH, ento EAX passaria a ter o valor $7AFD993C. Existe um registrador especial, o registrador de indicadores (flags), que armazena indicadores binrios alterados por instrues matemticas e lgicas ou explicitamente por cdigo, e que so normalmente usados em instrues de desvio condicional. O indicador carry tambm usado em algumas instrues de rotao e o indicador de direo utilizado em instrues envolvendo literais. Esse registrador no acessvel por nome como os demais registradores; mas pode ser copiado e restaurado atravs da pilha, utilizando PUSHF e POPF respectivamente, e pode tambm ser copiado e restaurado parcialmente atravs do registrador AH, utilizando LAHF e SAHF

respectivamente. Instrues Assembler ==================== Instrues assembler so dispostas em blocos asm..end blocks e tm a seguinte forma: [identificador:] [prefixo] opcode [operando1 [, operando2 [, ...]]] Onde opcode o nome da instruo como MOV, ADD, PUSH, etc. Instrues podem ser separadas por ponto e vrgula, quebras de linhas ou comentrios. A propsito, comentrios so no formato do Object Pascal, isto , o ponto e vrgula no considerado o incio de um comentrio at o final da linha, como no assembler tradicional. A seguir, um exemplo de bloco asm..end com vrios dos possveis tipos de instrues e separadores de comentrios: asm xchg ebx, edx; add eax, [ebx]; {ponto e vrgula separa declarao} // quebra de linha separa declarao mov ebx, p sub eax, [ebx] (*comentrio separa declarao*) mov ebx, edx end; A conveno utilizar quebras d elinhas para separao: asm xchg ebx, edx add eax, [ebx] mov ebx, p sub eax, [ebx] mov ebx, edx end; No cdigo da VCL, voc ver que os opcodes e nomes de registradores so escritos em maisculas e que instrues so indentadas em uma tabulao (normalmente equivalente a oito caracteres), mas utilizaremos outra conveno neste artigo. Blocos asm..end podem ocorrer em qualquer ponto do cdigo fonte onde uma declarao Pascal ordinria puder aparecer; alm disso, possvel termos rotinas 100% assembler se, ao invs de "begin", utilizarmos "asm": procedure teste; asm // declaraes assembler end; Note que as duas implementaes abaixo no so equivalentes: function f(parmetros): tipo; begin asm // declaraes assembler end;

end; function f(parmetros): tipo; asm // declaraes assembler end; A razo disso que o compilador realiza certas otimizaes quando implementamos rotinas inteiramente em assembler, sem utilizar um bloco begin..end. As etiquetas devem ser declaradas em uma seo Label, como em qualquer cdigo Object Pascal, a menos que foram prefixadas por "@@": function ENumeroMagico(x: integer): boolean; asm cmp eax, NumeroMagico je @@Bingo xor eax, eax ret @@Bingo: mov eax, 1 end; As etiquetas prefixadas por "@@" so locais ao bloco asm..end em que so usadas. Isto gerar um erro da compilaao: begin .... asm .... @@destino: .... end; .... asm .... jnz @@destino .... end; .... end;

// Error

Para corrigi-lo, necessitamos usar uma etiqueta convencional, local ao procedimento ou funo: label destino; begin .... asm .... destino: .... end; .... asm .... jnz destino ....

// Correto

end; .... end; Operandos ========= Certas vezes, um ou mais operandos so implcitos. Por exemplo, a instruo CDQ (Converta Dword para Qword) parece no utilizar operando algum; entretanto, essa instruo utiliza EDX e EAX: o bit mais alto de EAX, o bit de sinal, copiado para EDX de forma que, EDX:EAX passa a representar o inteiro em EAX convertido para Int64, onde EAX carrega os 32 bits menos significativos e EDX os 32 bits mais significativos. Para a maioria das instrues, os operandos so registradores. Por exemplo: mov eax, ecx copia o valor de ECX para EAX. Operandos podem conter valores imediatos: mov mov mov mov eax, 5 eax, 2 + 3 // expresso constante, resolvida na compilao al, 'A' // o cdigo ASCII de 'A' $41 (65) eax, 'ABC' // equivalente a MOV EAX, $00414243

Operandos tambm podem conter referncias de memria: mov [ebx], eax // EBX^ := EAX; Referncias de memria aparecem de vrias formas: mov eax, [$000FFFC] mov eax, [ebx] mov eax, [ebp-12] mov eax, mov eax, mov eax, mov eax, // // // // [ebp+ebx] // [ebp+ebx+8] // // [ebp+ebx*4] // // [ebp+ebx*4+8] // // // Endereo absoluto Registrador Registrador mais/menos deslocamento constante Registrador mais deslocamento em registro Registrador mais deslocamento em registro mais/menos deslocamento constante Registrador mais deslocamento em registro multiplicado por constante Registrador mais deslocamento em registro multiplicado por constante, mais/menos deslocamento constante

Os identificadores usuais do Pascal so traduzidos para uma das formas: mov eax, parmetro mov eax, varlocal mov eax, varglobal call procname Primeiro Exemplo ================ Agora que estamos prontos para aprender alguns opcodes, vamos aos // // // // mov eax, [ebp + deslocamento_constante] mov eax, [ebp - deslocamento_constante] mov eax, [endereo_absoulto] chama endereo absoluto

exemplos. Podemos comear com uma funo simples: function f(x: integer; y: integer): integer; // f(x,y) = (-x-y+5)*7 { begin Result := (-x - y + 5) * 7; end; } asm // os parmetros so passados em EAX (x) e neg eax // EAX := -EAX; // EAX sub eax, edx // EAX := EAX - EDX; // EAX add eax, 5 // EAX := EAX + 5; // EAX imul 7 // EAX := EAX * 7; // EAX end; Os trs em EAX, em EAX) segundo de fato

EDX (y); = -x = -x-y = -x-y+5 = (-x-y+5)*7

primeiros parmetros (da esquerda para a direita) so passados EDX e ECX. Para mtodos, o primeiro parmetro Self (passado e o primeiro parmetro explicitamente declarado , de fato, o parmetro (passado em EDX) e o segundo parmetro explcito o terceiro parmetro (passado em ECX).

O valor de retorno deve ser armazenado em EAX para valores ordinais de 32 bits (AX e AL devem ser utilizados para retornar valores de 16 e 8 bits respectivamente). Os comentrios explicam os opcodes de forma clara mas, para IMUL, temos que acrescentar duas explicaes: * IMUL considera os operandos (EAX e 7 no exemplo) como inteiros com sinal (devemos utilizar MUL quando os operandos no possurem sinal). * O resultado da multiplicao um inteiro de 64 bits sendo que os 32 bits mais significativos do resultado so armazenados em EDX. Multiplicaes so relativamente caras em termos de tempo de CPU e, por vezes, mais fcil substitui-las por deslocamentos de bits (quando a multiplicao ou diviso operarem com potncias de dois), somas e subtraes. Por exemplo: a * 7 = = = a * 7 = a a a a * (8 - 1) * 8 - a * 2^3 - a shl 3 - a

Ao invs de IMUL 7, podemos fazer o seguinte: mov ecx, eax // ECX := EAX; // ECX = shl eax, 3 // EAX := EAX shl 3; // EAX = sub eax, ecx // EAX := EAX - ECX; // EAX = // EAX = Vejamos outro exemplo: function resto(x: integer; y: integer): integer; // Retorna o resto de x dividido por y { begin Result := x mod y; -x-y+5 (-x-y+5)*8 (-x-y+5)*8 - (-x-y+5) (-x-y+5)*7

end; } asm // os parmetros so passados em EAX (x) e EDX (y); mov ecx, edx // ECX := EDX; // EDX = y cdq // EDX:EAX := Int64(EAX); // EAX = x idiv ecx // diviso inteira com sinal em 32 bits: // EAX := Int64(EDX:EAX) div integer(ECX); // EDX := Int64(EDX:EAX) mod integer(ECX); mov eax, edx // Result := EDX; // resto end; A Pilha ======= Quando um programa carregado, ele receve uma pilha, que uma regio de memria utilizada como uma estrutura LIFO, "Last In, First Out" (ltimo a chegar, primeiro a sair), controlada pelo registrador ESP que aponta para o topo dessa pilha. ESP inicia apontando para o final da regio de modo que, cada vez que empilhamos um novo valor de 32 bits, o registrador ESP decrementado em 4 (bytes) e o valor armazenado no local apontado por ESP. | | +-----------+ | | +-----------+ | $01234567 | <- ESP +-----------+ | | PUSH $89ABCDEF // SUB ESP,4; MOV [ESP],$89ABCDEF

| | +-----------+ | $89ABCDEF | <- ESP +-----------+ | $01234567 | +-----------+ | | De forma anloga, quando retiramos um valor de 32 bits da pilha, o valor recuperado do local apontado por ESP e ESP incrementado em 4 (bytes). POP EAX // MOV EAX,[ESP]; ADD ESP,4

| | +-----------+ +-----------+ | $89ABCDEF | EAX | $89ABCDEF | +-----------+ +-----------+ | $01234567 | <- ESP +-----------+ | | A pilha utilizada para armazenar endereos de retorno de rotinas, parmetros, variveis locais e resultados intermedirios. No exemplo a seguir, utilizamos a pilha para salvar o valor de um registrador para uso posterior:

function IntDiv(x: integer; y: integer; r: pinteger = NIL): integer; // Retorna o quociente inteiro x / y e o resto em r { begin Result := x div y; if r <> NIL then r^ := x mod y; end; } asm // os parmetros so passados em EAX (x), EDX (y) e ECX (r) push ecx // Salve ECX (r) para uso posterior mov ecx, edx // ECX := EDX; // ECX = y cdq // EDX:EAX := Int64(EAX); // EAX = x idiv ecx // diviso inteira com sinal em 32 bits: // EAX := Int64(EDX:EAX) div integer(ECX); // EDX := Int64(EDX:EAX) mod integer(ECX); pop ecx // Restaura ECX (ECX := r) cmp ecx, 0 // if ECX = NIL then jz @@end // goto @@end; mov [ecx], edx // ECX^ := EDX; // resto @@end: // identificador local (precedido por "@@") end; Note que, para cada PUSH que executamos, temos que executar um POP correspondente de modo que ESP fique inalterado (ESP um dos registradores que temos que preservar). A instruo CMP subtrai o segundo operador do primeiro (ECX-0 nesse caso), como a instruo SUB, mas o resultado no armazenado em lugar algum, ainda que o indicador de Zero (Zero flag) seja marcado (ligado) ou limpo (desligado) dependendo do resultado ser zero ou no, como em qualquer instruo lgica ou matemtica (com a exceo de certos casos). Podemos ento tirar vantagem desse fato e, ao invs de escrevermos cmp ecx, 0 podemos escrever or ecx, ecx // ECX := ECX or ECX;

O resultado de ECX Or ECX o prprio ECX; portanto, o valor armazenado em ECX o mesmo de antes, e como dissemos anteriormente o indicador de Zero ser marcado se o resultado for zero (isto , se ECX era zero). A instruo JZ, "Jump if Zero" (Desvie se Zero), desvia (salta) para o identificador indicado como operando se o valor do indicador de Zero estiver marcado (ligado) ou continua normalmente com o fluxo de execuo se o indicador de Zero estiver desmarcado (desligado). Passando Parmetros para a Pilha -------------------------------Voltemos para a pilha. Dissemos que os trs primeiros parmetros de uma rotina so passados em EAX, EDX e ECX; mas, o que acontece quando temos mais de trs parmetros? Parmetros adicionais so passados na pilha, da esquerda para a direita, de forma que o ltimo parmetro ser sempre o primeiro da pilha. Suponha que temos a seguinte funo

function Soma(a, b, c, d, e: integer): integer; begin Result := a + b + c + d + e; end; e queremos fazer a chamada Sum(1,2,3,4,5); Em assembler, faramos da seguinte forma: mov eax, 1 mov edx, 2 mov ecx, 3 push 4 push 5 call Sum A instruo CALL empilha o endereo de retorno na pilha e salta para (inicia a execuo) da funo. A instruo RET (RETorna) gerada pelo compilador quando o final de uma funo alcanado desempilha esse endereo da pilha e salta para ele para continuar a execuo a partir desse ponto. Note que quando empilhamos parmetros na pilha mas no os desempilhamos. Isso acontece pois limpar a pilha responsabilidade da funo chamada e no da funo que chama (exceto na conveno de chamada CDECL). Para limpar os parmetros, a instruo RET utilizada com um operando que indica o nmero de bytes que ESP deve ser incrementado (8 nesse caso j que ESP foi decrementado em 4 bytes para cada parmetro empilhado). O compilador fica encarregado dessa tarefa portanto no temos com que nos preocupar; mas, se voc utilizar a janela de depurao da CPU e encontrar uma instruo RET $08, agora voc j sabe do que se trata. Na entrada para Soma, a pilha estaria, em teoria, da seguinte forma: | | +-----------+ | Ret_Addr | <- ESP +-----------+ | $00000005 | (parmetro e) +-----------+ | $00000004 | (parmetro d) +-----------+ | | Quando uma funo tem parmetros na pilha (ou variveis locais), o compilador gera algumas instrues chamadas de "stack frame", quadro da pilha. Na entrada da funo (em "asm"), EBP empilhado de modo a ser preservado e ESP atribudo a ele; e, antes de deixar a funo, (em "end"), o valor original de EBP desempilhado: function Soma(a, b, c, d, e: integer): integer; asm // push ebp; mov ebp, esp; .... end; // pop ebp; ret 8; Assim, quando entramos em Soma, a pilha estaria de fato da seguinte

forma: | | +-----------+ | Orig. EBP | <- EBP, ESP +-----------+ | Ret_Addr | +-----------+ | $00000005 | <- EBP+8 (parmetro e) +-----------+ | $00000004 | <- EBP+12 (parmetro d) +-----------+ | | Em [EBP] encontramos o valor original de EBP que foi empilhado para ser preservado quando da construo do quadro de pilha; em [EBP+4] encontramos o endereo de retorno da rotina; em [EBP+8] encontramos o ltimo parmetro (o ltimo parmetro empilhado por ltimo e, por isso, o primeiro da pilha). O parmetro seguinte (da direita para a esquerda) fica em [EBP+12], e assim por diante se houvesse outros parmetros. Agora vamos escrever a rotina Soma em assembler: function Soma(a, b, c, d, e: integer): integer; { begin Result := a + b + c + d + e; end; } asm add eax, b add eax, c add eax, d add eax, e end; Note que no bloco asm..end ns utilizamos "b", "c", "d" e "e" ao invs de "EDX", "ECX", "[EBP+12]" e "[EBP+8]" respectivamente. Ns podemos fazer assim j que o compilador far as substituies adequadas. Variveis Locais na Pilha ------------------------Se nossa funo assembler inline tiver variveis locais, o compilador criar espao para essas variveis na pilha, movendo o ponteiro da pilha de modo que o quadro da pilha para uma funo com duas variveis locais inteiras seria: push ebp mov ebp, esp sub esp, 8 ... add esp, 8 pop ebp // Desloca ESP como se desempilhssemos 8 bytes

// Desloca ESP como se empilhssemos 8 bytes

Para o propsito do exemplo, aqui vai uma variao da rotina Soma acima,

utilizando duas variveis locais: function SomaL(a, b, c, d, e: integer): integer; var f, g: integer; { begin f := b + c; g := d + e; Result := a + f + g; end; } asm // push ebp; mov ebp, esp; sub esp, 8; add edx, ecx mov f, edx // b + c mov edx, d add edx, e mov g, edx // d + e add eax, f add eax, g end; // add esp, 8; pop ebp; ret 8 Nessa funo, a pilha teria o seguinte aspecto: | | +-----------+ | var. g | +-----------+ | var. f | +-----------+ | Orig. EBP | +-----------+ | Ret_Addr | +-----------+ | Param e | +-----------+ | Param d | +-----------+ | | O Que Vem Agora? ================ Na continuao deste artigo, aprenderemos mais instrues e veremos como passar e retornar outros tipos de parmetros, como trabalhar com arrays, como acessar campos de registros e objetos, como chamar mtodos e mais. Nesse captulo iremos aprender algumas novas instrues assembler e o bsico da manipulao de strings ANSI, tambm chamadas de strings longas. Novos opcodes ============= Abaixo os opcodes introduzidos neste atrigo: * JL (Jump if Lower, desvie se menor): A descrio mais adequada levaria

<- EBP-8, ESP <- EBP-4 <- EBP

<- EBP+8 <- EBP+12

muito tempo para ser explicada, ento vamos dizer que JL salta (desvia) para o label especificado desde que na operao CMP (ou SUB) anterior o primeiro operando seja menor que o segundo numa comparao com sinal: // if signed(op1) < signed(op2) then goto @@label; cmp op1, op2 jl @@label JG (Jump if Greater, desvie se maior), JLE (Jump if Lower or Equal, desvie se menor ou igual) e JGE (Jump if Greater or Equal, desvie se maior ou igual) completa a famlia de desvios condicionais para comparaes com sinal. * JA (Jump if Above, desvie se maior): salta (desvia) para o label especificado desde que na operao CMP (ou SUB) anterior o primeiro operando seja maior que o segundo numa comparao sem sinal: // if unsigned(op1) > unsigned(op2) then goto @@label; cmp op1, op2 ja @@label JB (Jump if Below, desvie se menor), JBE (Jump if Below or Equal, desvie se menor ou igual) e JAE (Jump if Above or Equal, desvie se maior ou igual) completam a famlia de desvios condicionais para comparaes sem sinais. * LOOP: Decrementa ECX e, se no for zero, desvia para o label indicado. LOOP @@label o equivalente mais curto e rpido de: dec ecx jnz @@label Examplo: xor eax, eax mov ecx, 5 @@label: add eax, ecx loop @@label // EAX := EAX xor EAX; // EAX := 0; // ECX := 5; // EAX := EAX + ECX; // Executado 5 vezes // Dec(ECX); if ECX <> 0 then goto @@label; // ECX := ECX - 1; // if ECX <> 0 then goto @@label

// EAX seria 15 (5+4+3+2+1) Trabalhando com strings ANSI ============================ Uma varivel string representada por um ponteiro de 32 bits. Se a string vazia (''), ento o ponteiro nil (zero), caso contrrio, esse ponteiro aponta para o primeiro caractere dessa string. O tamanho da string e a contagem de referncia so dois inteiros em deslocamentos negativos a partir do primeiro byte da string: +-----------+ | s: string |-------------------+ +-----------+ | V --+-----------+-----------+-----------+---+---+---+---+---+---+---+-| allocSiz | refCnt | length | H | e | l | l | o | ! | #0| --+-----------+-----------+-----------+---+---+---+---+---+---+---+-(longint) (longint) (longint)

\-----------------v-----------------/ StrRec record const skew = sizeof(StrRec); // 12 Quando passamos uma string como um parmetro para uma funo, o que de fato passado o ponteiro de 32 bits. Os valores string so um pouco mais complicados de explicar. A rotina que chamou a rotina que retorna a string deve passar- como ltimo e invisvel parmetro da chamada, um tipo PString-o endereo de uma varivel string que receber o resultado da funo. d := Uppercase(s); // Internamente convertido para: Uppercase(s, @d); Se o resultado da funo usado em uma expresso ao invs de ser atribudo diretamente varivel, a rotina que chama deve utilizar uma varivel temporria incializada com nil (string vazia). O compilador faz tudo isso automaticamente no nosso cdigo Object Pascal mas, se temos que fazer isso por conta prpria se optarmos por escrever cdigo assembler que chame rotinas que retornam strings. Para algumas tarefas, no podemos chamar as clssicas funes de string diretamente. Por exemplo, a funo Length no o nome de uma funo de verdade,. uma construo interna do prprio compilador e o compilador gera o cdigo para a funo apropriada, dependendo do parmetro ser uma string ou um array dinmico. Em assembler, ao invs de Lenght, teramos que usar a funo LStrLen (declarada na unidade System) para obter o tamanho da string. Existem mais coisas que deveramos saber a respeito das strings mas o que temos j suficiente para um primeiro exemplo. Verso Assembler de Uppercase ============================= Eis a declarao da funo: function AsmUpperCase(const s: string): string; O parmetro "s" ser passado em EAX e o endereo de "Result" ser passado como o segundo parmetro, ou seja, em EDX. Basicamente a funo deve fazer: 1) Obter o comprimento da string a converter 2) Alocar memria para a string convertida 3) Copiar os caracteres um a um, convertidos para maisculas 1) Obter o comprimento da string a converter -------------------------------------------Faremos isso atravs de uma chamada a System.@LStrLen. A funo espera a string em EAX (ela j est l) e o resultado ser colocado em EAX; ento, temos que salvar o valor de EAX (o parmetro "s") em algum lugar antes de chamar a funo de modo que "s" no seja perdido. Podemos salvar numa varivel local "src". J que funes so livres para utilizar os registradores EAX, ECX e EDX, presumimos que o valor em EDX ("@Result") poderia tambm ser destrudo aps uma chamada a System.@LStrLen, de modo

que til salvar esse valor numa varivel local, por exemplo, "psrc". O resultado da chamada a System.@LStrLen, deixado em EAX, servir como parmetro da chamada a System.@LStrSetLength (para alocar memria para o contedo da string de resultado), como contador dos bytes a copiar, de modo que esse valor tambm deve ser salvo, por exemplo, na varivel "n": var pdst: Pointer; // Endereo da string resultado src: PChar; // String de origem n: Integer; // Comprimento da string de origem asm // O endereo da string de resultado passado em EDX. // Salvamos esse valor na varivel pdst: mov pdst, edx // pdst := EDX; // Salvamos EAX (s) na varivel local (src) mov src, eax // src := EAX; // n := Length(s); call System.@LStrLen mov n, eax // EAX := LStrLen(EAX); // n := EAX;

2) Alocar memria para a string convertida -----------------------------------------A alocao realizada atravs de uma chamada a System.@LStrSetLength. O procedimento espera dois parmetros: o endereo da string (que salvamos em "pdst") e o comprimento da string (que est em EAX). // SetLength(pdst^, n); // Alocar a string de resultado mov edx, eax // EDX := n; // Segundo parmetro p/LStrSetLength mov eax, pdst // EAX := pdst; // Primeiro parmetro p/LStrSetLength call System.@LStrSetLength // LStrSetLength(EAX, EDX); 3) Copiar os caracteres um a um, convertidos para maisculas -----------------------------------------------------------Se o comprimento da string era zero, j terminamos: // if n = 0 then exit; mov ecx, n // ECX := n; test ecx, ecx // Fazer and de ECX com ECX para definir flags // (ECX inalterado) jz @@end // Ir para @@end se o flag zero est marcado (ECX=0) No sendo esse o caso, devemos copiar os caracteres de uma string para a outra, convertendo-os para maisculas conforme necessrio. Ns vamos utilizar ESI e EDX para apontar para os caracteres da string de origem e destino respectivamente, AL para carregar os caracteres da string de origem e realizar a mudana antes de armazen-los na string de destino e ECX para controlar a instruo de LOOP que contar os caracteres. J que ESI um registro que tem que ser preservado, devemos salvar seu valor para restaur-lo mais tarde. Decidi salvar ESI colocando-o na pilha. push esi // Salve ESI na pilha

// Inicializar ESI e EDX mov eax, pdst // EAX := pdst; // Endereo da string de resultado

mov esi, src mov edx, [eax]

// ESI := src; // String de origem // EDX := pdst^; // String de resultado

@@cycle: mov al, [esi] // AL := ESI^; // if Shortint(AL) < Shortint(Ord('a')) then goto @@nochange cmp al, 'a' jl @@nochange // AL in ['a'..#127] // if Byte(AL) > Byte(Ord('a')) then goto @@nochange cmp al, 'z' ja @@nochange // AL in ['a'..'z'] sub al, 'a'-'A' // Dec(AL, Ord('a')-Ord('A')); @@nochange: mov [edx], al // EDX^ := AL; inc esi // Inc(ESI); inc edx // Inc(EDX); loop @@cycle // Dec(ECX); if ECX <> 0 then goto cycle pop esi @@end: end; // Restaurar ESI da pilha

Вам также может понравиться