我是这种语言的完全新手,我正在努力学习它。
这是我第一次使用低级语言。
这是我不完整的代码:
;------------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个小时。
任何帮助都将受到高度赞赏。
答案 0 :(得分:1)
装配可能是与朋友交往的挑战。主要是因为给你非常基本的构建块,内存段,要使用的段内的地址,处理器寄存器以加载值和从中获取结果,基本系统调用,以及调用这些系统调用来操作的方法当前处理器寄存器中的值。然后你可以做其余的事情。我将尝试概述您在下面的基本组装调用中可以使用的思维过程。
有许多网络文章提供了一小部分难题,但很少提供相当完整的语言概述。这就是我在评论中提到The Art of Assembly Language Programming的原因,我将在此再次提及。如果你花时间阅读这些章节,你就可以很好地处理如何使用装配。
在装配中,通常有几种方法可以解决任何问题。打印0-9
也不例外。在我的评论中,我解释了0-9
的数值与可打印的ASCII字符'0'-'9'
之间的差异。要将值输出到屏幕,您必须将ASCII值写入stdout
(或文件描述符号1
,其中stdin - 0
,stdout - 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
)到您的循环的开头标签(例如jmp
或loop
),然后每次递减next:
中的值直到你点击looplbl:
。
完成循环后,为了完成,您可以将程序的退出值加载到ecx
和0
到ebx
并调用内核中断退出。现在,此过程还有许多其他子问题基础知识。这只是概述了解决方案的一种方法。您只需要阅读并调查剩余部分,因为它远远超出了这篇文章的内容。
要获得帮助,请完成以下示例。它只是从结束的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种方法可以做到这一点,有些我肯定会好得多。但实际上只需要一步一步地关注内存中每个寄存器和字节的作用。