在汇编中打印 0-9
Printing 0-9 in Assembly
我是这门语言的新手,我正在努力学习它。
这是我第一次处理低级语言。
这是我的不完整代码:
;------------Block 1----------
.386
.model flat,stdcall
option casemap:none
;------------Block 2----------
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;------------block 3----------
.data
first DW 1 ; increment this value
;------------Block 4----------
.data?
retvalue dd ?
;------------Block 5----------
.code
start:
mov ecx,10
mov eax, '1'
l1:
nop ;Add code here
loop l1
xor eax,eax
invoke ExitProcess,eax
end start
其 64 位架构与 Intel 处理器。
我参考了这些文章,但其中 none 似乎适合我的代码架构。
- http://www.dailyfreecode.com/code/print-digits-0-1-9-1741.aspx
- Assembly, printing ascii number
- Outputting variable values in x86 asm
这让我恼火了 4 个小时。
任何帮助将不胜感激。
结交朋友可能是一个挑战。主要是因为你得到了非常基本的构建块来使用,内存段,要使用的段内的地址,处理器寄存器加载值并从中获取结果,基本系统调用,以及调用这些系统调用以进行操作的方法当前在处理器寄存器中的值。然后由您来完成剩下的工作。我将尝试大致概述您在处理下面的基本汇编调用时可以使用的思维过程。
有许多网络文章提供了一小部分拼图,但很少有文章提供对语言的相当完整的概述。这就是为什么我在评论中提到了The Art of Assembly Language Programming,我会在这里再次提到它。如果您花时间浏览这些部分,您将会很好地掌握如何使用汇编。
在汇编中,通常有几种方法可以解决任何问题。打印 0-9
也不例外。在我的评论中,我解释了 0-9
的数值和可打印的 ASCII 字符 '0'-'9'
之间的区别。为了将值输出到屏幕,您必须将 ASCII 值写入 stdout
(或文件描述符编号 1
,其中 stdin - 0
、stdout - 1
、stderr - 2
).
要将值写入 stdout
,您必须使用正确的处理器寄存器中的正确值进行正确的 sys_write
系统调用。 (您可以在 unistd_32.h
(32 位)或 unistd_64.h
(64 位)中找到正确的系统调用,位于您的发行版相关包含目录中,通常为 /usr/include/asm
或 /usr/include/asm-x86
) 您只需要担心 2 个即可满足您的需求,sys_write
(编号 4
)和 sys_exit
(编号 1
)。
什么处理器寄存器?您知道系统调用号将进入第一个寄存器 eax
。值得庆幸的是,您通常可以从 C man page
中找出相关命令的其余部分。对于编写 man 2 write page
会有所帮助(您可以以类似的方式使用所有系统功能的手册页)。查看 write
函数声明。您的寄存器值通常是函数所需的参数(按顺序)。例如
ssize_t write(int fd, const void *buf, size_t count);
| | |
register: ebx ecx edx
现在你知道在每个寄存器中写入一个字符到stdout
(文件描述符编号),执行写入,你生成一个内核打断它。 (对于 x86,即 int 80h
)。看看如何将一个字符写入 stdout
:
mov eax, 4 ; linux (sys_write) in eax
mov ebx, 1 ; fileno in ebx (stdout)
mov ecx, achar ; move achar address to ecx
mov edx, 1 ; num chars to write in edx
int 0x80 ; kernel interrupt
(其中achar
是要写入字符的内存地址)
如评论中所述,您可以从数字 0-9
开始,然后将 '0'
添加到值中以获得 ASCII 字符值(或者您可以简单地 or
'0'
的值来完成同样的事情)。您也可以只从 ASCII 值 '0'
开始,打印它,将其值递增 1
(得到 '1'
,等等),总共执行 10 次。 (所以想到了一个循环)
我会让你进一步阅读汇编中的循环,但基本方案是将循环计数(在你的情况下为 10
)加载到 ecx
,然后跳转(jmp
)(或loop
)到循环的开头标签(例如next:
或looplbl:
),然后递减[=中的值49=] 每次通过直到你点击 0
。
当你的循环完成后,为了结束,你将程序的退出值加载到 ebx
并将 sys_exit
加载到 eax
并调用内核中断退出。现在这个过程有很多额外的子问题基础。这只是对解决方案的一种方法的概述。您只需要阅读和研究其余部分,因为它远远超出了这个 post 所能塞满的范围。
为了提供帮助,请完成以下示例。它只是从结束 ASCII 值 '9'
中减去起始 ASCII 值 '0'
(然后添加 1
总共 10 个字符,并将该值用于循环计数)。然后循环 10 次,从打印 '0'
开始,然后将 1
添加到每次循环打印的前一个值,直到打印完所有 '0'-'9'
。然后它打印一个 newline
这样数字就不会与您的提示挤在同一行并退出:
section .data
achar db '0'
nwln db 0xa
section .text
global _start ; must be declared for using gcc
_start: ; tell linker entry point
xor eax, eax ; zero eax register
mov al, byte '9' ; move byte '9` (57) to al
sub al, byte '0' ; subtract byte '0' (48) from al
inc al ; add 1 to al (for total chars)
xor ecx, ecx ; zero exc
mov cx, ax ; move result to cx (loop count)
next:
push ecx ; save value of ecx on stack
mov eax, 4 ; linux (sys_write) in eax
mov ebx, 1 ; fileno in ebx (stdout)
mov ecx, achar ; move achar address to ecx
mov edx, 1 ; num chars to write in edx
int 0x80 ; kernel interrupt
pop ecx ; restore loop count
mov dx, [achar] ; move value of achar to dx
inc byte [achar] ; increment value of achar (next char)
loop next ; jump to next:
mov eax, 4 ; linux (sys_write) in eax
mov ebx, 1 ; fileno in ebx (stdout)
mov ecx, nwln ; move nwln (newline) to ecx
mov edx, 1 ; num chars to write in edx
int 0x80 ; kernel interrupt
xor ebx, ebx ; zero ebx (for exit code 0)
mov eax, 1 ; system call number (sys_exit)
int 0x80 ; kernel interrupt
(注意:你可以输出ASCII字符集中的所有可打印字符,只需将上面代码中的'9'
替换为'~'
即可)
Compile/Link
这是使用 nasm 汇编器的 compile/link 示例。如果您使用的是 fasm 或 yasm,情况会类似。我将所有目标文件写入 obj
子目录,将二进制文件写入 bin
子目录,以减少混乱。
nasm -f elf -o obj/prn_digits_32.o prn_digits_32.asm
ld -m elf_i386 -o bin/prn_digits_32 obj/prn_digits_32.o
输出
$ ./bin/prn_digits_32
0123456789
希望对您有所帮助。如果您有任何疑问,请告诉我。可能有 20 多种方法可以做到这一点,我相信有些方法要好得多。但这实际上只是一个一步一个脚印并注意内存中每个寄存器和字节在做什么的问题。
我是这门语言的新手,我正在努力学习它。
这是我第一次处理低级语言。
这是我的不完整代码:
;------------Block 1----------
.386
.model flat,stdcall
option casemap:none
;------------Block 2----------
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;------------block 3----------
.data
first DW 1 ; increment this value
;------------Block 4----------
.data?
retvalue dd ?
;------------Block 5----------
.code
start:
mov ecx,10
mov eax, '1'
l1:
nop ;Add code here
loop l1
xor eax,eax
invoke ExitProcess,eax
end start
其 64 位架构与 Intel 处理器。
我参考了这些文章,但其中 none 似乎适合我的代码架构。
- http://www.dailyfreecode.com/code/print-digits-0-1-9-1741.aspx
- Assembly, printing ascii number
- Outputting variable values in x86 asm
这让我恼火了 4 个小时。
任何帮助将不胜感激。
结交朋友可能是一个挑战。主要是因为你得到了非常基本的构建块来使用,内存段,要使用的段内的地址,处理器寄存器加载值并从中获取结果,基本系统调用,以及调用这些系统调用以进行操作的方法当前在处理器寄存器中的值。然后由您来完成剩下的工作。我将尝试大致概述您在处理下面的基本汇编调用时可以使用的思维过程。
有许多网络文章提供了一小部分拼图,但很少有文章提供对语言的相当完整的概述。这就是为什么我在评论中提到了The Art of Assembly Language Programming,我会在这里再次提到它。如果您花时间浏览这些部分,您将会很好地掌握如何使用汇编。
在汇编中,通常有几种方法可以解决任何问题。打印 0-9
也不例外。在我的评论中,我解释了 0-9
的数值和可打印的 ASCII 字符 '0'-'9'
之间的区别。为了将值输出到屏幕,您必须将 ASCII 值写入 stdout
(或文件描述符编号 1
,其中 stdin - 0
、stdout - 1
、stderr - 2
).
要将值写入 stdout
,您必须使用正确的处理器寄存器中的正确值进行正确的 sys_write
系统调用。 (您可以在 unistd_32.h
(32 位)或 unistd_64.h
(64 位)中找到正确的系统调用,位于您的发行版相关包含目录中,通常为 /usr/include/asm
或 /usr/include/asm-x86
) 您只需要担心 2 个即可满足您的需求,sys_write
(编号 4
)和 sys_exit
(编号 1
)。
什么处理器寄存器?您知道系统调用号将进入第一个寄存器 eax
。值得庆幸的是,您通常可以从 C man page
中找出相关命令的其余部分。对于编写 man 2 write page
会有所帮助(您可以以类似的方式使用所有系统功能的手册页)。查看 write
函数声明。您的寄存器值通常是函数所需的参数(按顺序)。例如
ssize_t write(int fd, const void *buf, size_t count);
| | |
register: ebx ecx edx
现在你知道在每个寄存器中写入一个字符到stdout
(文件描述符编号),执行写入,你生成一个内核打断它。 (对于 x86,即 int 80h
)。看看如何将一个字符写入 stdout
:
mov eax, 4 ; linux (sys_write) in eax
mov ebx, 1 ; fileno in ebx (stdout)
mov ecx, achar ; move achar address to ecx
mov edx, 1 ; num chars to write in edx
int 0x80 ; kernel interrupt
(其中achar
是要写入字符的内存地址)
如评论中所述,您可以从数字 0-9
开始,然后将 '0'
添加到值中以获得 ASCII 字符值(或者您可以简单地 or
'0'
的值来完成同样的事情)。您也可以只从 ASCII 值 '0'
开始,打印它,将其值递增 1
(得到 '1'
,等等),总共执行 10 次。 (所以想到了一个循环)
我会让你进一步阅读汇编中的循环,但基本方案是将循环计数(在你的情况下为 10
)加载到 ecx
,然后跳转(jmp
)(或loop
)到循环的开头标签(例如next:
或looplbl:
),然后递减[=中的值49=] 每次通过直到你点击 0
。
当你的循环完成后,为了结束,你将程序的退出值加载到 ebx
并将 sys_exit
加载到 eax
并调用内核中断退出。现在这个过程有很多额外的子问题基础。这只是对解决方案的一种方法的概述。您只需要阅读和研究其余部分,因为它远远超出了这个 post 所能塞满的范围。
为了提供帮助,请完成以下示例。它只是从结束 ASCII 值 '9'
中减去起始 ASCII 值 '0'
(然后添加 1
总共 10 个字符,并将该值用于循环计数)。然后循环 10 次,从打印 '0'
开始,然后将 1
添加到每次循环打印的前一个值,直到打印完所有 '0'-'9'
。然后它打印一个 newline
这样数字就不会与您的提示挤在同一行并退出:
section .data
achar db '0'
nwln db 0xa
section .text
global _start ; must be declared for using gcc
_start: ; tell linker entry point
xor eax, eax ; zero eax register
mov al, byte '9' ; move byte '9` (57) to al
sub al, byte '0' ; subtract byte '0' (48) from al
inc al ; add 1 to al (for total chars)
xor ecx, ecx ; zero exc
mov cx, ax ; move result to cx (loop count)
next:
push ecx ; save value of ecx on stack
mov eax, 4 ; linux (sys_write) in eax
mov ebx, 1 ; fileno in ebx (stdout)
mov ecx, achar ; move achar address to ecx
mov edx, 1 ; num chars to write in edx
int 0x80 ; kernel interrupt
pop ecx ; restore loop count
mov dx, [achar] ; move value of achar to dx
inc byte [achar] ; increment value of achar (next char)
loop next ; jump to next:
mov eax, 4 ; linux (sys_write) in eax
mov ebx, 1 ; fileno in ebx (stdout)
mov ecx, nwln ; move nwln (newline) to ecx
mov edx, 1 ; num chars to write in edx
int 0x80 ; kernel interrupt
xor ebx, ebx ; zero ebx (for exit code 0)
mov eax, 1 ; system call number (sys_exit)
int 0x80 ; kernel interrupt
(注意:你可以输出ASCII字符集中的所有可打印字符,只需将上面代码中的'9'
替换为'~'
即可)
Compile/Link
这是使用 nasm 汇编器的 compile/link 示例。如果您使用的是 fasm 或 yasm,情况会类似。我将所有目标文件写入 obj
子目录,将二进制文件写入 bin
子目录,以减少混乱。
nasm -f elf -o obj/prn_digits_32.o prn_digits_32.asm
ld -m elf_i386 -o bin/prn_digits_32 obj/prn_digits_32.o
输出
$ ./bin/prn_digits_32
0123456789
希望对您有所帮助。如果您有任何疑问,请告诉我。可能有 20 多种方法可以做到这一点,我相信有些方法要好得多。但这实际上只是一个一步一个脚印并注意内存中每个寄存器和字节在做什么的问题。