在装配中打印0-9

时间:2016-02-23 02:48:59

标签: assembly i386

我是这种语言的完全新手,我正在努力学习它。

这是我第一次使用低级语言。

这是我不完整的代码:

;------------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位架构。

我提到了这些文章,但它们似乎都不适合我的代码架构。

这让我烦恼了4个小时。

任何帮助都将受到高度赞赏。

1 个答案:

答案 0 :(得分:1)

装配可能是与朋友交往的挑战。主要是因为给你非常基本的构建块,内存段,要使用的段内的地址,处理器寄存器以加载值和从中获取结果,基本系统调用,以及调用这些系统调用来操作的方法当前处理器寄存器中的值。然后你可以做其余的事情。我将尝试概述您在下面的基本组装调用中可以使用的思维过程。

有许多网络文章提供了一小部分难题,但很少提供相当完整的语言概述。这就是我在评论中提到The Art of Assembly Language Programming的原因,我将在此再次提及。如果你花时间阅读这些章节,你就可以很好地处理如何使用装配。

在装配中,通常有几种方法可以解决任何问题。打印0-9也不例外。在我的评论中,我解释了0-9的数值与可打印的ASCII字符'0'-'9'之间的差异。要将值输出到屏幕,您必须将ASCII值写入stdout(或文件描述符号1,其中stdin - 0stdout - 1,{{1} })。

要将值写入stderr - 2,您必须使用正确的处理器寄存器中的正确值进行正确的stdout系统调用。 (您可以在位于发行版相关包含目录中的sys_write(32位)或unistd_32.h(64位)中找到正确的系统调用,通常为unistd_64.h或{{1} })您只需要担心2,/usr/include/asm(号码/usr/include/asm-x86)和sys_write(号码4)。

什么处理器注册?您知道系统调用号将放在第一个寄存器sys_exit中。值得庆幸的是,您通常可以从C 1中找出有问题的命令。对于写入eax将有所帮助(您可以以类似的方式使用手册页来获取所有系统功能)。查看man page函数声明。您的寄存器值通常是函数所需的参数(按顺序)。 e.g。

man 2 write page

现在您知道在每个寄存器中将字符写入write(文件描述符编号)的内容,为了执行写操作,您生成内核中断来执行此操作。 (对于x86,即ssize_t write(int fd, const void *buf, size_t count); | | | register: ebx ecx edx )。看看如何将一个字符写入stdout

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添加到该值以获取ASCII字符值(或者您只需0-9 '0'完成同样的事情)。您也可以从ASCII值or开始,打印它,将其值增加'0'(以获得'0'等等)并总共执行10次。 (所以想到一个循环)

我会让你进一步阅读汇编中的循环,但基本方案是将你的循环计数(在你的情况下为1)加载到'1'然后跳转(10 )(或ecx)到您的循环的开头标签(例如jmploop),然后每次递减next:中的值直到你点击looplbl:

完成循环后,为了完成,您可以将程序的退出值加载到ecx0ebx并调用内核中断退出。现在,此过程还有许多其他子问题基础知识。这只是概述了解决方案的一种方法。您只需要阅读并调查剩余部分,因为它远远超出了这篇文章的内容。

要获得帮助,请完成以下示例。它只是从结束的ASCII值sys_exit中减去起始ASCII值eax(然后将'0'添加为总共10个字符,并将该值用于循环计数)。然后通过打印'9'开始循环10次,并将1添加到每次通过循环打印的前一个值,直到所有'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字符集中的所有可打印字符

<强>编译/链接

这是nasm汇编程序的编译/链接示例。如果你使用fasm或yasm,它将是类似的。我将所有目标文件写入'~'子目录,将我的二进制文件写入'9'子目录,以减少混乱。

obj

<强>输出

bin

我希望这会有所帮助。如果您有疑问,请告诉我。可能有20种方法可以做到这一点,有些我肯定会好得多。但实际上只需要一步一步地关注内存中每个寄存器和字节的作用。