不使用C printf的“Hello World”功能

时间:2014-03-09 10:17:11

标签: linux assembly x86 nasm

  

已更新

这是我与NASM合作的第二天。在彻底了解了这个

之后
section .programFlow
    global _start
    _start:
        mov edx,len
        mov ecx,msg
        mov ebx,0x1    ;select STDOUT stream
        mov eax,0x4    ;select SYS_WRITE call
        int 0x80       ;invoke SYS_WRITE
        mov ebx,0x0    ;select EXIT_CODE_0
        mov eax,0x1    ;select SYS_EXIT call
        int 0x80       ;invoke SYS_EXIT
section .programData
    msg: db "Hello World!",0xa
    len: equ $ - msg

我想将这些东西包装在一个汇编函数中。网络上的所有(或大部分)示例都使用extern并调用printf的{​​{1}}函数(请参阅下面的代码) - 我不希望如此。我想学习在汇编中创建一个“Hello World”函数,而不使用C C(甚至其他外部函数调用)。

printf

更新

我正在为Linux练习程序集,但由于我没有Linux机器,因此我在这里运行汇编代码compile_assembly_online

3 个答案:

答案 0 :(得分:2)

int 0x80无法在Windows或DOS中运行,因为它是Linux的东西。这就是第一个必须改变的事情。

就在Windows下执行此操作而言,在某些时候您将需要调用Windows API函数,例如(在本例中)WriteConsole()。那是根据需要绕过C库。

使用操作系统来完成输出到“屏幕”的繁重工作,但这与int 0x80相同,可能需要它是Linux,Windows还是DOS。

如果正版DOS,那么您最好的起点就是优秀Ralf Brown's Interrupt List,特别是Int21/Fn9

答案 1 :(得分:2)

假设您的意思是在Windows命令提示符环境中,写入标准输出:

由于那些提供了旧DOS环境的虚拟化版本,我相信你可以使用旧的DOS中断:

int 21, function 9可以输出一个字符串:将AH设置为9,将DS:DX设置为以$结尾的字符串,然后触发中断。

int 21, function 2可以输出单个字符,因此如果您需要输出$(或者您不想要Ctrl + C和此类检查),可以重复使用该字符。 AH到2,DL到ASCII(我期待)字符代码,并触发中断。

答案 2 :(得分:1)

我想指出,Nasm“知道”某些部分名称 - “。text”,“。data”和“.bss”(还有一些你还不需要的部分)。领先的'。'是必需的,名称区分大小写。正如您在第一个示例中所做的那样,使用其他名称可能“有效”但可能无法为您提供所需的“属性”。例如,节.programData is going to be read-only. Since you don't try to write to it this isn't going to do any harm... but节.data`应该是可写的。

尝试学习Linux的asm而不能尝试它一定很困难。也许这个在线网站对你来说足够了。有一个名为“andlinux”的东西(我认为)可以让你在Windows中运行Linux程序。或者你可以在“虚拟机”中运行Linux。或者你可以在你的许多备用驱动器之一上创建一个分区并实际安装Linux。

对于DOS,有DosBox ......或者你可以在其中一个额外的分区上安装“真正的DOS”。从那里,你可以在B800h:xxxx写“直接到屏幕”。 (“字符”的一个字节,“颜色”的下一个字节)。如果你想在没有操作系统帮助的情况下这样做,那可能就是你想要的。在保护模式OS中,忘了它。他们受到美国的保护!

通常,您可能只想知道如何编写子程序。我们可以编写一个子程序,其中“msg”和“len”硬编码到其中 - 不是很灵活。或者我们可以编写一个带有两个参数的子程序 - 在寄存器或堆栈中。或者我们可以编写一个子程序,它需要一个以零结尾的字符串(printf,sys_write不会),并计算出edx中的长度。如果这就是您需要帮助的地方,那么我们就会分心int 80h vs int 21h vs WriteFile。你可能需要再问一次......

编辑:好的,一个子程序。不明显的部分是call将返回地址(调用之后的指令地址)放在堆栈上,ret获取地址返回堆栈,所以我们不想改变ss:sp之间的位置。我们可以更改它,但我们需要将它放回到ret之前的位置。

; purpose: to demonstrate a subroutine
; assemble with: nasm -f bin -o myfile.com myfile.asm
; (for DOS)

; Nasm defaults to 16-bit code in "-f bin" mode
; but it won't hurt to make it clear
bits 16 

; this does not "cause" our code to be loaded
; at 100h (256 decimal), but informs Nasm that
; this is where DOS will load a .com file
; (needed to calculate the address of "msg", etc.)
org 100h

; we can put our data after the code
; or we can jump over it
; we do not want to execute it!
; this will cause Nasm to move it after the code
section .data
    msg db "Hello, World!", 13, 10, "$"
    msg2 db "Goodbye cruel world!", 13, 10, "$"

section .text
; execution starts here
    mov dx, msg ; address/offset of msg
    call myprint
; "ret" comes back here
; no point in a subroutine if we're only going to do it once
    mov dx, msg2
    call myprint
; when we get back, do something intelligent

exit:
    mov ah. 4Ch ; DOS's exit subfunction
    int 21h
; ---------------------------------------

; subroutines go here, after the exit
; we don't want to "fall through" into 'em!
myprint:
; expects: address of a $-terminated string in dx
; returns: nothing
    push ax ; don't really need to do this
    mov ah, 9 ; DOS's print subfunction
    int 21h ; do it
    pop ax ; restore caller's ax - and our stack!
    ret
; end of subroutine

这是未经测试的,但受到错别字和愚蠢的逻辑错误的影响,它“应该”起作用。我们可以使它变得更复杂 - 将参数传递给堆栈而不是dx。我们可以为Linux提供一个例子(同样的一般想法)。我建议小步走......