Author Topic: How to include procedures from other files ?....  (Read 23357 times)

nasmkid

  • Guest
How to include procedures from other files ?....
« on: November 03, 2010, 07:12:46 AM »
Hi All,

I have two files
1.asm
2.asm

Code: [Select]
==============================================
Contents of "1.asm"
==============================================
;--------------- my procedures ----------------

;----------------------------------------------
; procedure "r_start" start
;----------------------------------------------

r_start:
..start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,stacktop

ret
;----------------------------------------------
; procedure "r_start" end
;----------------------------------------------



;----------------------------------------------
; procedure "r_print" start
;----------------------------------------------
r_print:
mov ah, 02h
a:
mov dl, [si] ; move the character to 'dl' register
inc si ; increment si register

cmp dl, 0 ; compare 0 (null) is received
je b

int 21h ; print the char
jmp a ; continue to lable 'a'

b:
ret ; procedure end
;----------------------------------------------
; procedure "r_print" end
;----------------------------------------------

Code: [Select]
==============================================
Contents of "2.asm"
==============================================
%include "1.asm"

; define data segment
segment data

hello db 'Hello World', 0


; define code segment
segment code

call r_start

mov si,hello

call r_print

mov ah,0x4c
int 0x21


; define stack segment
segment stack stack
resb 64
stacktop:

==============================================

Commands used to compile and run on DOS (command prompt)

>nasm -fobj 1.asm
>alink 1.obj -o 1.exe
>1.exe

NO OUTPUT OBSERVED, WHAT IS THE PROBLEM ? PLS HELP ME
« Last Edit: November 03, 2010, 03:09:38 PM by Keith Kanios »

Offline Rob Neff

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 429
  • Country: us
Re: How to include procedures from other files ?....
« Reply #1 on: November 03, 2010, 12:16:28 PM »

Commands used to compile and run on DOS (command prompt)

>nasm -fobj 1.asm
>alink 1.obj -o 1.exe
>1.exe

NO OUTPUT OBSERVED, WHAT IS THE PROBLEM ? PLS HELP ME


Since you're including 1.asm within 2.asm change your build sequence:

nasm -fobj 2.asm
alink 2.obj -o 2.exe
2.exe

I'm not familiar with using alink but doesn't it require a start label ( eg: _main or __start ) in your code?

Offline cm

  • Jr. Member
  • *
  • Posts: 65
Re: How to include procedures from other files ?....
« Reply #2 on: November 03, 2010, 01:00:42 PM »
The ..start label needs to be in 2.asm and you have to assemble 2.asm as Rob pointed out.
C. Masloch

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: How to include procedures from other files ?....
« Reply #3 on: November 03, 2010, 07:05:31 PM »
Hi nasmkid,

Well, now we know "what OS?". :)

"%include" essentially does a "cut-and-paste". It's one way to combine files, but probably not the one you want.

Each program wants one (and only one!) entrypoint. A .com file starts at the beginning of the file - no options. Typically, you'd need to declare "global _start" or "global main", but in "-f obj" output format (only!), Nasm knows the special symbol "..start", and knows it's supposed to be global. (other assemblers use "end foo" to declare that "foo" is the entrypoint!) This doesn't need to be the first thing in your source file - you can put subroutines first - but it'll be the first code that gets executed.

You've got a "..start" label in 1.asm:

Code: [Select]
==============================================
Contents of "1.asm"
==============================================
;--------------- my procedures ----------------

;----------------------------------------------
; procedure "r_start" start
;----------------------------------------------

r_start:
..start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,stacktop

ret
;----------------------------------------------
; procedure "r_start" end
;----------------------------------------------


When dos loads a .com file, it sets segment registers to the chosen segment - they're all the same. Not true for an .exe file - ds and es point to the "Program Segment Prefix" (a 256 byte area preceeding our actual code), so we need to correct that - I'd do es, also, although you may not need it:

Code: [Select]
..start:
mov ax,data
mov ds,ax
                mov es, ax

I know the example in the Friendly Manual shows setting up ss and sp, too, but dos takes care of that for you. You're better off without it, IMO (I'll remove it from the manual "one of these days"... I delay because I'm not sure why it's there - maybe there's some reason I'm not aware of...) For now, ditch that part.

Slight diversion: "call" puts the return address (the address of the next instruction after "call") on the stack. "ret" gets the return address off the stack so it knows where to "ret" to. If the return address isn't the next thing on the stack when we get to "ret", we go to the wrong place (crash, usually, although we can deliberately put a "wrong" address on the stack and "ret" to it). So... if you had managed to "call r_start" (that code doesn't ever get executed), and then meddled with sp, it wouldn't work anyway. Generally, one does not "ret" from the "..start" label - there's no return address on the stack!

Further diversion: "main" (often spelled "_main") is a special word to C. Ordinarilly, C links in some "startup code" which calls main, after putting a pointer to environment variables, a pointer to command line parameters, and the parameter count on the stack. So you can "ret" from "main". In assembly, "main" is not a "special word", so you can use it as an ordinary label. Some people like to call their entrypoint "main". Personally, I don't like to use "main" unless I've got a "C-style main" - at least a return address, and preferably "argc", "**argp", and "**envp" above it...

To get back to what you're trying to do, if you don't want to "ret" from the "..start" label, what do you want to do? That stuff in 2.asm, I guess. You don't want to call r_start - that's already been done. Say hello and exit, I guess - perhaps not at the same time. So let's make 'em into separate procedures. Declare 'em "global" (some assemblers use "public"). Since "r_print" is in 1.asm, we need to declare it "extern". "global" is for stuff in this file that needs to be visible outside this file, "extern" is for stuff outside this file that needs to be used in this file. Nasm just tells the linker about it, and the linker hooks 'em up.

Code: [Select]
;Contents of "2.asm"
;==============================================
; %include "1.asm"

global sayhi
global exit
extern r_print

; define data segment
segment data

hello db 'Hello World', 0


; define code segment
segment code

; call r_start

;-----------------------------------
sayhi:
mov si,hello

call r_print
        ret
;-----------------------------------

;------------------------
exit:
mov ah,0x4c
int 0x21
; does not return
;----------------------------------

; define stack segment
segment stack stack
resb 64

Now 1.asm can look like this:

Code: [Select]
;==============================================
;Contents of "1.asm"
;==============================================
;--------------- my procedures ----------------

global r_print
extern sayhi
extern exit

segment code  ; better make sure they're the same name.

;----------------------------------------------
; procedure "r_start" start
;----------------------------------------------

r_start:
..start:
mov ax,data
mov ds,ax
                mov es, ax ; might as well do es, too

; mov ax,stack
; mov ss,ax
; mov sp,stacktop

; ret
                call sayhi
                call exit
                ; does not return
;----------------------------------------------
; procedure "r_start" end
;----------------------------------------------



;----------------------------------------------
; procedure "r_print" start
;----------------------------------------------
r_print:
mov ah, 02h
a:
mov dl, [si] ; move the character to 'dl' register
inc si ; increment si register

cmp dl, 0 ; compare 0 (null) is received
je b

int 21h ; print the char
jmp a ; continue to lable 'a'

b:
ret ; procedure end
;----------------------------------------------
; procedure "r_print" end
;----------------------------------------------

That's untested(!), but should work if I haven't made any errors. Actually, the "my procedures" label indicates that you probably want the "..start" label in 2.asm, as Christian suggested. You could "call r_start" as a subroutine to initialize ds (and es, if you want), but definitely don't fiddle with ss/sp or it won't return! (put the "ret" back, in that case)

Then assemble both files, and tell alink about 'em.

Code: [Select]
nasm -f obj 1.asm
nasm -f obj 2.asm
alink -oEXE -o outname.exe 1.obj 2.obj
outname

(if memory serves, alink has got kind of a weird interface: "-o" (or "/o"?) without a space after it indicates the output format - "-oEXE" may be the default - and with a space, indicates the output file name. "-entry" (or "/entry") if you want an entrypoint other than "..start". If all else fails, RTFM.)

In any case, "global" and "extern" are the keys to including procedures from other files.

Best,
Frank


Offline cm

  • Jr. Member
  • *
  • Posts: 65
Re: How to include procedures from other files ?....
« Reply #4 on: November 03, 2010, 10:22:05 PM »
I know the example in the Friendly Manual shows setting up ss and sp, too, but dos takes care of that for you. You're better off without it, IMO (I'll remove it from the manual "one of these days"... I delay because I'm not sure why it's there - maybe there's some reason I'm not aware of...)

It's always better to be aware of what you set up in what way. For any moderately complex program the default stack (whether the program is loaded as .COM or .EXE format file) won't be sufficient. The .EXE format allows you to let DOS set up a stack with a pre-determined size, but the executable header fields necessary for that are handled by the linker so you need to know how to tell the linker about what you want. Or what the linker will default to. (DOS never takes care of anything for you automatically ;-).) For simple .EXE programs, I find it easier to set up some stack space in a BSS (uninitialized) section and point ss:sp there manually. This is what the example in the manual does.
C. Masloch

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: How to include procedures from other files ?....
« Reply #5 on: November 03, 2010, 11:41:14 PM »
Mmmm... the manual no longer says what I remember it saying, so I guess it's a "fixed issue" (or my memory's even worse than I thought). It used to be just like nasmkid's example:

Code: [Select]
; define stack segment
segment stack stack
resb 64
stacktop:

And:

Code: [Select]
..start:
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,stacktop

Which doesn't do any harm (unless you were foolish enough to reserve an "odd" number of bytes - in which case dos would "round it off" and this code would make sp odd again - or if you do it in a subroutine), but if you declare your stack as:

Code: [Select]
section my_stack_name stack
resw 100h  ; let's make it a little bigger

Nasm should "tell the linker what we want to do", and the linker should put this info in the MZ header, and dos should initialize ss:sp for us (may not work for all linkers - alink, val, mslink, and tlink are okay, as I recall). Nothing wrong with declaring your own stack in .bss (except that "-f obj" doesn't know about .bss) - or even in data initialized to some value - and setting ss:sp yourself. But you wouldn't want to do it in a subroutine!

In 32-bit code (or 64-bit code, I assume), the OS generally tells us where the stack is, so we can forget about this (along with a bunch of other 16-bit junk we're happy to forget!).

Best,
Frank


Offline cm

  • Jr. Member
  • *
  • Posts: 65
Re: How to include procedures from other files ?....
« Reply #6 on: November 04, 2010, 02:49:30 PM »
But you wouldn't want to do it in a subroutine!

Ah, right. I didn't say that so you could, uh, figure it out yourself. Gives a pretty obvious crash anyway.

Quote
In 32-bit code (or 64-bit code, I assume), the OS generally tells us where the stack is, so we can forget about this (along with a bunch of other 16-bit junk we're happy to forget!).

Insert obligatory comment here.
C. Masloch