http://en.wikipedia.org/wiki/BIOS_interrupt_callPretty good explanation (pretty girl, too!), but it does not mention that these BIOS interrupts are 16-bit code. They are, and they just won't work in 32-bit code (trust me - I've tried it!). I haven't tried 64-bit code, but they won't work there, either.
This puts you in a bad situation - kinda like having one foot in each of two canoes.
Your proposed code has got the "right idea", but there are some errors, which you would easily find if you could try 'em.
I suppose the place to start is with "entrypoint". You seem to assume that "main" is the entrypoint. This is true in some cases, but not all. In particular, in a .com executable format, the entrypoint is always the beginning of the file - at "section .text". (Nasm will move "section .data" and "section .bss", if you have one, to the end of the file) Your first example doesn't say "section .text", but Nasm will default to that if you don't say. So the very first thing that is executed will be your "toupper" subroutine. Well... since it doesn't end in "ret" it isn't really a "subroutine". This will work (I think - I'm not testing it either!) for the first character. After printing the first character, you'll "fall through" to "main". pushad and popad push and pop 32-bit registers. This ("probably") won't do any harm, but won't do any good either - I'd use just pusha/popa in 16-bit code (which you're stuck with because of the BIOS interrupts). Then you'll "call toupper", which will get and print the second character... and this will go on "forever", without ever hitting the popa(d) or the "ret" in "main". It may appear to work, but it isn't really doing what you intend! You can fix this by adding "jmp main" (first line of the file in your first example, right after "section .text" in your second example) - and put "ret" at the end of your subroutine! As written, it'll only do one character, but it's a start.
Your second example is a mixture of 16- and 32-bit code, and has some other problems...
section .data
string db "word"
As I mentioned, Nasm will move this to the end, so it won't get executed. However, in order to find "string", Nasm will need to be told "org 0x100" (for a .com file)! Also, you might want to terminate "word" with a zero, or otherwise indicate the length, so "upper" knows when it's done.
section .text
jmp main ; <--- so we start at "main"!
upper:
mov esi, dword [esp]
This isn't right! You pushed "string", and it was at "[esp]" at that point, but "call" pushed the return address on the stack! It may not be "obvious" that call and ret use the stack, but they do! "[esp + 2]" might work - if you're lucky...
mov ah, 0x08
mov al, byte [esi] ;one character
int 0x10 ;i'm not sure if that works-to read character which is on screen already ("word")
No, this won't work.
cmp al, 'a'
jge .big ;if al is or bigger than 'a'-97
jmp .print ;if not
There are a couple of subtle errors here. For one thing, you've avoided trying to uppercase anything less than 'a', which is good, but there are a few characters beyond 'z' ('{', '|', '}', and '~') which you also don't want to convert to uppercase. You probably won't encounter any of these characters in your testing, so the bug will never show up, but it's still a bug!
The other thing is even more subtle. "jg" and "jl" test for a signed comparison. 0xFF is treated as -1, and is "less" than zero (or 'a'). "ja" and "jb" are for unsigned comparisons. You will probably never encounter anything that would be "negative" (if treated as signed) in this code, so it won't do any harm, but it still isn't really "right". "jae" would be better. These issues occur in your first example, too. Not too big a deal...
.big:
sub al, 32
jmp .print
.print:
Without the "jmp", you'd "fall through" into ".print" anyway. Does no harm, but it's not needed.
mov ah, 0x0e
int 0x10
add esi, 1 ;next character
loop .upper ;again
The "loop" instruction works on cx. Since we don't know what's in cx, this will loop an indeterminate number of times. If the length of "word" was in cx, it would work, or if "word" were zero-terminated, we could check for that (instead of using "loop").
Again, you've failed to end your subroutine with "ret"!
main:
pushad
push dword string
call upper
add esp, 4
popad
ret
This is okay, except for being a mixture of 32- and 16-bit code. It'll ("probably") work okay anyway.
There's a "gotcha" in converting this to straight 16-bit code. While "mov esi, [esp]" is a legal instruction (although it wants to be "[esp + 2]"), "mov si, [sp]" is not! Addressing modes are quite limited in 16-bit code.
You can use 32-bit instructions in 16-bit code (and get the 32-bit addressing modes - nice!), but there's a potential "gotcha" there, too. If the "offset" exceeds 64k, it will cause a "segment overrun exception" which dos doesn't handle (probably causing a "hang") - I don't know what Dosbox would do with it. I don't think this exception will occur in your code, but it's a risk. A "proper" 16-bit subroutine would look like this:
upper:
; create a "stack frame"
push bp
mov bp, sp
; get your parameter
mov si, [bp + 4] ; 2 for the return address, 2 for "push bp"
mov al, [si]
; etc.
.done:
mov sp, bp
pop bp
ret ;!
That's not tested either, but I think I've got it right (famous last words!). It would really help you if you could try out your code. If you can get Nasm to work at all, it'll produce a .com file - even in a 32- or 64-bit system. You can copy that to somewhere in your Dosbox directory (folder), or perhaps you can run it under Dosbox from where it sits.
It is possible to create a 16-bit .exe format executable - doesn't have to be a .com file. That would require slightly different code, and would require a linker (or including a macro file). Since you're forced to use 16-bit code by using the BIOS interrupts, I'd stick with the .com format if you can.
You're doing a really good job for just "writing it on paper", but there are a few problems which you would easily discover if you could "try it". See if you can find a way to do so, if you can!
Best,
Frank