Skip to main content
+minor improvements
Source Link

Actually, strictly speaking it's not the assembler but the linker who might have a word to say about the sequence, and the linkers that I know don't mind the sequence (even the Keil Linker for 8051, L51, which is really weird stuff).

; Returns the ASCII value of the input digit.
; @param eax digit (value in the range of [0..9]) which is converted into an ASCII digit.
; @return eax ASCII value (in the range of ['0'..'9']).
; @warning If eax is out of the supported range, the behavior is undefined.
to_ascii:
    add eax, '0'
    ret
; Returns the ASCII value of the input digit.
; @param eax digit (value in the range of [0..9]) which is converted into an ASCII digit.
; @return eax ASCII value (in the range of ['0'..'9']).
; @warning If eax is out of the supported range, the behavior is undefined.
to_ascii
    add eax, '0'
    ret

Actually, strictly speaking it's not the assembler but the linker who might have a word to say about the sequence, and the linkers that I know don't mind the sequence (even the Keil Linker for 8051, L51, which is really weird stuff).

; Returns the ASCII value of the input digit.
; @param eax digit (value in the range of [0..9]) which is converted into an ASCII digit.
; @return eax ASCII value (in the range of ['0'..'9']).
; @warning If eax is out of the supported range, the behavior is undefined.
to_ascii:
    add eax, '0'
    ret
Fix wrong fix for exit code
Source Link
mov ebx, 1
mov eax, 4
int 80h ; outputs "Blast off!"

mov eaxebx, 0
mov eax, 1
int 80h ; exits program
mov ebx, 1
mov eax, 4
int 80h ; outputs "Blast off!"

mov eax, 0
int 80h ; exits program
mov ebx, 1
mov eax, 4
int 80h ; outputs "Blast off!"

mov ebx, 0
mov eax, 1
int 80h ; exits program
Source Link

Section sequence

That's rather a matter of taste. Programmers used to 68k usually use the sequence .text, .data, .bss, programmers used to Java usually use the sequence .data, .bss, .text. In the assemblers known to me it doesn't make any difference, so it's rather a matter of taste or corporate policy.

Functional code

Breaking up bigger chunks of code into smaller parts very often makes sense because very often it increases maintainability.

Basically in Assembler you do a lot of trade-off between maintainability, speed and size.

Your functions are not universally reusable, for example your function to_ascii performs no sanity checks. In that case, it doesn't always make sense, especially when the function body is just on instruction.

However, as far as I'm aware, on a modern member of the 80x86 family, instructions like call and ret basically come free of cost, thanks to the pipeline and branch prediction, so maybe there is no loss of speed, just of space.

Redundancy

The sequence

mov ecx, num
mov ebx, 1
mov eax, 4
int 80h ; output the current number

is redundant. I don't think it's entirely bad. Compared to having a single-instruction function to_ascii it looks, however, inconsistent.

I'd extract this into something like a print_number function.

Exit status

(You fixed this meanwhile.)

Your exit code looks like this:

mov ebx, 1
mov eax, 4
int 80h ; outputs "Blast off!"

mov eax, 1
int 80h ; exits program

The contents of register ebx are still 1 when you call exit. This is probably wrong. I do not see any indication why you would want to indicate that your program exits unsuccessfully. You probably want to insert a mov ebx, 0 like this:

mov ebx, 1
mov eax, 4
int 80h ; outputs "Blast off!"

mov eax, 0
int 80h ; exits program

Asymmetry in to_number vs. to_ascii

to_number accesses [num] itself, whereas in the case of to_ascii, [num] is accessed by the caller. This is asymmetric, a form of inconsistent. In bigger programs this might lead to confusion with respect to how APIs work.

Name log

Nowadays when you say log people think of generic logging along the lines of Logging APIs like log4j or sclog4c.

I'd rather call this function something else, in order to prevent confusion.

Formatting

I'm not sure whether this is a convention, but I'd do this:

  • blank line before every new function (i.e. after a ret that is not followed by code which could be reached by a conditional jmp before the ret).
  • Use local labels for loop and branch labels inside a function.
  • Write a small function header on top of each function which describes input, output and access to globals (especially side-effects).

Function header

Here's a sample function header for to_ascii

; Returns the ASCII value of the input digit.
; @param eax digit (value in the range of [0..9]) which is converted into an ASCII digit.
; @return eax ASCII value (in the range of ['0'..'9']).
; @warning If eax is out of the supported range, the behavior is undefined.
to_ascii
    add eax, '0'
    ret

Local labels vs Global labels

AFAIK this is also true for NASM:

  • Local labels start with '.' and are valid until the next global label.
  • Global labels do not start with '.'.

Looking at your code, subtract should be a local label and thus renamed to .subtract.

This is not mandatory, and there is a valid interpretation of your code which includes tail recursion and last call optimization in which your code and label structure are valid.

It's just a bit surprising (maybe even positively) to see such constructs in assembler.

Conditional jumps

At the end of subtract you have two conditional jumps:

jne subtract
je blast_off

While this code is correct, I think the following would be more clear:

je blast_off
jmp subtract

Or in case you want to optimize for speed

jne subtract
jmp blast_off

The point is maintainability. I found it less error-prone to have the last conditional jmp, which semantically is identical to an unconditional jmp as unconditional jmp indeed. When you change the conditions, there is less necessity of changing the last jmp, and therefore less risk of getting code which would just go haywire for running unintentionally into the next function, in case the current function doesn't end with ret itself.

Structured Programming regarding _start / subtract?

I personally would almost always follow structured programming even in Assembly language.

At _start / subtract, this is not done because you call subtract. Maybe that call even is unintentional, as I cannot see where subtract would ever return.

Independently of that, a construct like

    call foo
foo:
    more code

looks confusing, at least to me. First, foo is called, and everything that is called is expected to return, and then when foo returned, we would run it once more.

I guess in this case, best is to simply remove the call subtract statement.

Structured Programming regarding _start / blast_off?

A similar problem is there with _start and blast_off. blast_off is not called, so it is not a function - or to be seen as last-call optimization. I'd actually avoid putting the good-case exit anywhere outside start.

In general, I'd avoid jumping over functions. I feel jumps should be more local than calls, not the other way round. The reason is that jumps are where we program conditions and loops, which is transfer of control inside a function, whereas calls is where we call subroutines, which is the transfer of control to another function. I find it confusing if the transfer of control inside a function is "bigger" in distance than the transfer of control to another function.

Output

The output looks like this:

10987654321Blast off!

You might want to put space in between.

Makefile

You might want to use a Makefile for this.

I wrote the following Makefile for building and running this myself:

AS:=nasm
ASFLAGS:=-f elf 
LINK.o:=ld
LOADLIBES:=-m elf_i386 -s

.PHONY: all 
all: blast_off

blast_off: blast_off.o

.PHONY: run
run: blast_off
        ./$^

Disclaimer

I am new to NASM / x86 / x64 assembler myself. I'm very experienced in 680x0, ARM and somewhat experienced in 6502, Z80, 8051, 80251 and CalmRISC but, as said, not (yet) in x86 / x64.