4

UPDATED

It's my second day working with NASM. After thoroughly understanding this

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

I wanted to wrap this stuff inside an assembly function. All (or most of) the examples on the web are using extern and calling printf function of C (see code below) - and I don't want that. I want to learn to create a "Hello World" function in assembly without using C printf (or even other external function calls).

global  _main
extern  _printf

section .text
    _main:
        push    message
        call    _printf
        add     esp, 4
        ret
section .data
    message: db "Hello, World", 10, 0

Update

I am practicing assembly for Linux, but since I do not own a Linux box, I am running my assembly code here compile_assembly_online.

Patt Mehta
  • 4,110
  • 1
  • 23
  • 47
  • So the short version of this question is: "How do I output to the console on Windows from assembly code without using C's `printf`?" Right? – T.J. Crowder Mar 09 '14 at 10:23
  • purely in assembly code – Patt Mehta Mar 09 '14 at 10:25
  • 1
    @GLES: What about the Windows API? If that's an option [there are answers for that here](http://stackoverflow.com/questions/1023593/how-to-write-hello-world-in-assembler-under-windows). – T.J. Crowder Mar 09 '14 at 10:26
  • You won't be able to run 16 bit code on Windows, it is not supported anymore since a long time. Therefore you need an emulator, such as DosBox. – edmz Mar 09 '14 at 10:29
  • As a warning, do not use comments to link to your questions and ask people to answer them. I've removed these comments across the site. – Brad Larson Mar 09 '14 at 22:07

3 Answers3

2

int 0x80 won't work in Windows or DOS simply because it's a Linux thing. So that's the first thing that has to change.

In terms of doing it under Windows, at some point you're going to need to call a Windows API function, such as (in this case) WriteConsole(). That's bypassing the C library as desired.

It does use the OS to do the heavy lifting in getting output to the "screen" but that's the same as int 0x80 and is probably required whether it's Linux, Windows or DOS.

If it is genuine DOS, your best place to start is the excellent Ralf Brown's Interrupt List, specifically Int21/Fn9.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • Thanks for the answer! @|"int 0x80 won't work in Windows" - I've updated the question. I want to write the code for DOS and Linux. – Patt Mehta Mar 09 '14 at 10:38
  • 1
    @GLES, then you need int80 for Linux (or SYSENTER or more modern kernels) and you already know how that's done. Or you need Int21/Fn9 for DOS. I would think that should be more than enough for you to progress to the next stage. – paxdiablo Mar 09 '14 at 10:40
2

Assuming you mean in a Windows command prompt environment, writing to standard out:

Since those provide a virtualized version of the old DOS environment, I believe you can use the old DOS interrupts for it:

int 21, function 9 can output a string: Set AH to 9, DS:DX to a string terminated with a $, and trigger the interrupt.

int 21, function 2 can output a single character, so you could use that repeatedly if you need to output $ (or you don't want Ctrl+C and such checking). AH to 2, DL to the ASCII (I expect) character code, and trigger the interrupt.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks for the answer! Please also suggest how to write assembly function for doing the same (in DOS environment). – Patt Mehta Mar 09 '14 at 10:36
  • 1
    @GLES: I haven't written any DOS assembly in 20 years, I just remembered there was an interrupt for standard out. But if you do the most cursory search, you'll find an example. In fact, I did a cursory search, and found...[one right here on SO](http://stackoverflow.com/a/284819/157247). – T.J. Crowder Mar 09 '14 at 10:39
  • 1
    :D thanks again! but how to wrap that code inside a `function` purely made in assembly (for linux x86)? – Patt Mehta Mar 09 '14 at 10:45
  • @GLES: That question makes no sense. You've asked specifically how to do this under DOS (or Windows, it depends on the edit). Now you're asking how to wrap up the code to do it in one OS in a function to use on another. – T.J. Crowder Mar 09 '14 at 10:46
  • 1
    I'm extremely sorry for the confusion. Please read the update. I am learning to do it for both - DOS and Linux. I do not own a Linux box, so I'm assembling the code here - www.compileonline.com/compile_assembly_online.php – Patt Mehta Mar 09 '14 at 10:50
  • @GLES: Again, that has nothing to do with doing this under DOS. So apparently they're using Linux for that service. To test the DOS version, use a DOS or Windows box. – T.J. Crowder Mar 09 '14 at 10:51
  • I have made the final update to the question. Thanks in advance! :) – Patt Mehta Mar 09 '14 at 11:00
1

I want to point out that Nasm "knows" certain section names - ".text", ".data", and ".bss" (a couple others that you don't need yet). The leading '.' is required, and the names are case sensitive. Using other names, as you've done in your first example, may "work" but may not give you the "attributes" you want. For example, section .programDatais going to be read-only. Since you don't try to write to it this isn't going to do any harm... butsection .data` is supposed to be writable.

Trying to learn asm for Linux without being able to try it out must be difficult. Maybe that online site is enough for you. There's a thing called "andlinux" (I think) that will let you run Linux programs in Windows. Or you could run Linux in a "virtual machine". Or you could carve out a parttion on one of your many spare drives and actually install Linux.

For DOS, there's DosBox... or you could install "real DOS" on one of those extra partitions. From there, you can write "direct to screen" at B800h:xxxx. (one byte for "character" and the next for "color"). If you want to do this "without help from the OS", that may be what you want. In a protected mode OS, forget it. They're protected from US!

Maybe you just want to know how to write a subroutine, in general. We could write a subroutine with "msg" and "len" hard coded into it - not very flexible. Or we could write a subroutine that takes two parameters - either in registers or on the stack. Or we could write a subroutine that expects a zero-terminated string (printf does, sys_write does not) and figure out the length to put in edx. If that's what you need help with, we've gotten distracted talking about int 80h vs int 21h vs WriteFile. You may need to ask again...

EDIT: Okay, a subroutine. The non-obvious part of this is that call puts the return address (the address of the instruction right after the call) on the stack, and ret gets the address to return to off the stack, so we don't want to alter where ss:sp points in between. We can change it, but we need to put it back where it was before we hit the 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

That's untested, but subject to typos and stupid logic errors, it "should" work. We can make it more complicated - pass the parameter on the stack instead of just in dx. We can provide an example for Linux (same general idea). I suggest taking it in small steps...

tshepang
  • 12,111
  • 21
  • 91
  • 136
Frank Kotler
  • 3,079
  • 2
  • 14
  • 9
  • Thanks a lot for your help :) |"we've gotten distracted talking about int 80h vs int 21h vs WriteFile" - exactly :D .. I found this example - http://stackoverflow.com/questions/2249624/assembly-language/2249673 - do you think it'll suffice? – Patt Mehta Mar 10 '14 at 04:02
  • @GLES The example does not, strictly speaking, show a subroutine. It does show how to print a message (in DOS) without using printf. Do YOU think it'll suffice? – Frank Kotler Mar 10 '14 at 23:30
  • No, it won't. Please can you provide one (even if it's a link to a web page etc.). Reflect the change in your answer too... I'll *mark it as the answer* :) – Patt Mehta Mar 11 '14 at 04:02
  • And I'm already thankful to you for at least responding :) The other two users don't even care. Take your time, but please help. Thanks! :-) – Patt Mehta Mar 11 '14 at 04:04