1

我正在尝试在 64 位 Linux 上使用 NASM 学习汇编。

我设法制作了一个读取两个数字并将它们相加的程序。我意识到的第一件事是该程序仅适用于一位数字(和结果):

; Calculator

SECTION .data
    msg1    db  "Enter the first number: "
    msg1len equ $-msg1
    msg2    db  "Enter the second number: "
    msg2len equ $-msg2
    msg3    db  "The result is: "
    msg3len equ $-msg3

SECTION .bss
    num1    resb    1
    num2    resb    1
    result  resb    1

SECTION .text
    global main

main:
    ; Ask for the first number
    mov EAX,4
    mov EBX,1
    mov ECX,msg1
    mov EDX,msg1len
    int 0x80

    ; Read the first number
    mov     EAX,3
    mov     EBX,1
    mov ECX,num1
    mov EDX,2
    int 0x80

    ; Ask for the second number
    mov EAX,4
    mov EBX,1
    mov ECX,msg2
    mov EDX,msg2len
    int 0x80

    ; Read the second number
    mov     EAX,3
    mov     EBX,1
    mov ECX,num2
    mov EDX,2
    int 0x80

    ; Prepare to announce the result
    mov EAX,4
    mov EBX,1
    mov ECX,msg3
    mov EDX,msg3len
    int 0x80

    ; Do the sum
    ; Store read values to EAX and EBX
    mov EAX,[num1]
    mov EBX,[num2]

    ; From ASCII to decimal
    sub EAX,'0'
    sub EBX,'0'

    ; Add
    add EAX,EBX

    ; Convert back to EAX
    add EAX,'0'

    ; Save the result back to the variable
    mov [result],EAX

    ; Print result
    mov EAX,4
    mov EBX,1
    mov ECX,result
    mov EDX,1
    int 0x80

如您所见,我为第一个数字保留一个字节,为第二个数字保留一个字节,为结果保留一个字节。这不是很灵活。我想添加任何大小的数字。

我应该如何处理这个?

4

1 回答 1

4

首先,您正在生成一个 32 位程序,而不是 64 位程序。这没有问题,因为如果 32 位程序是静态链接的(您就是这种情况)或安装了 32 位共享库,Linux 64 位可以运行它们。

您的程序包含一个真正的错误:您正在从 RAM 中的 1 字节字段读取和写入“EAX”寄存器:

mov EAX, [num1]

这通常适用于 little-endian 计算机 (x86)。但是,如果您要读取的字节位于程序的最后一个内存页的末尾,您将收到总线错误。

更关键的是写命令:

mov [result], EAX

此命令将覆盖“结果”变量后面的 3 个字节的内存。如果您通过额外的字节扩展您的程序:

num1 resb 1
num2 resb 1
result resb 1
newVariable1 resb 1

您将覆盖这些变量!要更正您的程序,您必须使用 AL(和 BL)寄存器而不是完整的 EAX 寄存器:

mov AL, [num1]
mov BL, [num2]
...
mov [result], AL

您的程序中的另一个发现是:您正在从文件句柄 #1 中读取。这是标准输出。您的程序应该从文件句柄 #0(标准输入)读取:

mov EAX, 3 ; read
mov EBX, 0 ; standard input
...
int 0x80

但现在实际问题的答案:

C 库函数(例如 fgets())使用缓冲输入。一开始这样做会有点复杂,因此一次读取一个字节可能是可能的。

思考“我将如何使用像 C 这样的高级语言来解决这个问题”的方式。如果您不在汇编程序中使用库,则只能将系统调用(第 2 节手册页)用作函数(例如,您不能使用“fgets()”而只能使用“read()”)。

在您的情况下,从标准输入读取数字的 C 程序可能如下所示:

int num1;
char c;
...
num1 = 0;
while(1)
{
    if(read(0,&c,1)!=1) break;
    if(c=='\r' || c=='\n') break;
    num1 = 10*num1 + c - '0';
}

现在你可能会想到汇编代码(我通常使用 GNU 汇编,它有另一种语法,所以这段代码可能包含一些错误):

c resb 1
num1 resb 4

...

    ; Set "num1" to 0
  mov EAX, 0
  mov [num1], EAX
    ; Here our while-loop starts
next_digit:
    ; Read one character
  mov EAX, 3
  mov EBX, 0
  mov ECX, c
  mov EDX, 1
  int 0x80
    ; Check for the end-of-input
  cmp EAX, 1
  jnz end_of_loop
    ; This will cause EBX to be 0.
    ; When modifying the BL register the
    ; low 8 bits of EBX are modified.
    ; The high 24 bits remain 0.
    ; So clearing the EBX register before
    ; reading an 8-bit number into BL is
    ; a method for converting an 8-bit
    ; number to a 32-bit number!
  xor EBX, EBX
    ; Load the character read into BL
    ; Check for "\r" or "\n" as input
  mov BL, [c]
  cmp BL, 10
  jz end_of_loop
  cmp BL, 13
  jz end_of_loop
    ; read "num1" into EAX
  mov EAX, [num1]
    ; Multiply "num1" with 10
  mov ECX, 10
  mul ECX
    ; Add one digit
  sub EBX, '0'
  add EAX, EBX
    ; write "num1" back
  mov [num1], EAX
    ; Do the while loop again
  jmp next_digit
    ; The end of the loop...
end_of_loop:
    ; Done

用更多位数写十进制数字更难!

于 2013-09-11T05:24:45.010 回答