Personal.X-Istence.com

Bert JW Regeer (畢傑龍)

Setting up jails with multiple IPs and providing it with internet access

I have multiple VMs up and running and generally I have an internal to the VMs only network set up, in the current project I am working on I have three completely different networks set up.

  1. DHCP assigned network that routes to the outside network on the host (192.168.0.1/24)
  2. An internal to the VM's network interface (10.99.12.1/24)
  3. A network that is tied to the hosts wireless network (setup to be an AP) (10.99.11.1/24)

So within my FreeBSD VM I have the following: em0, em1, em2 as "network cards". In my FreeBSD VM, I am creating two jails that are going to contain different pieces of server software. The jails need ip's in the following ranges, 10.99.12.1/24 (to connect to another VM), 10.99.11.1/24 (Wifi network) and then it needs to have an IP address that is considered "local" and we can NAT on, so in addition to the above networks we also needed to add another network. The easiest way to create new interface with IP address' is using a bridge. They can be assigned IP addresses, which is all we need.

At this point inside of a jail it kinda looks like this:

em0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
    options=209b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,WOL_MAGIC>
    ether 52:54:00:6b:6a:49
    media: Ethernet autoselect (1000baseT <full-duplex>)
    status: active
em1: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
    options=209b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,WOL_MAGIC>
    ether 52:54:00:2f:a5:55
    inet 10.99.12.4 netmask 0xffffffff broadcast 10.99.12.4
    media: Ethernet autoselect (1000baseT <full-duplex>)
    status: active
em2: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
    options=209b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,WOL_MAGIC>
    ether 52:54:00:bb:b6:43
    inet 10.99.11.14 netmask 0xffffffff broadcast 10.99.11.14
    media: Ethernet autoselect (1000baseT <full-duplex>)
    status: active
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
    options=3<RXCSUM,TXCSUM>
bridge0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
    ether da:de:63:c9:55:6c
    inet 10.0.1.4 netmask 0xffffffff broadcast 10.0.1.4
    id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15
    maxage 20 holdcnt 6 proto rstp maxaddr 100 timeout 1200
    root id 00:00:00:00:00:00 priority 32768 ifcost 0 port 0

em0 doesn't have an address since that is our DHCP address, em1, em2 and bridge0 all have IP addresses, in the jail configuration the bridge0 IP address is listed first, and thus when something binds or sends it sends from that IP address. The thing is that at this point the jail does not have internet access.

Jail's inherit the routing table from their host environment (yes, you can compile in more routing tables ... but I don't want to modify stock), any traffic that goes to the outside is going to go out over the default route, thing is you'd expect that traffic to show up on bridge0 where the IP address exists, instead using tcpdump you see it show up on em0, which is where the default route is. I would have expected having to do NAT on the bridge, not just the external interface.

Here is the pf configuration.

ext_if="em0"
jail_if="bridge0"

set optimization aggressive
set block-policy drop
set skip on lo

nat on $ext_if from $jail_if:network:0 to any -> ($ext_if)

pass quick all

And we've got internet within our jail. I am looking forward to setting this all up when the new VIMAGE stuff lands in 9.0-RELEASE so that I can have virtual network cards that are not tied to a physical network card!

Moving from `mod_fastcgi` to `mod_fcgid`

Update: I've since moved to a PHP-FPM with mod_fastcgi based setup which works much better than mod_fcgid could ever dream of, taking process management away from Apache and putting it where it actually makes sense has improved server load and improved website speed. See my new PHP-FPM with mod_fastcgi post for more information.

--

Errors, all they cause is trouble. The dreaded 500 error showed up when visiting my favourite tech website, the one I am the administrator for. This time I had enough, there was going to be no more playing around with PHP settings, attempting to figure out why PHP was suddenly dying and or why mod_fastcgi refused to retry a select() when it failed due to a system signal.

The server in question runs Apache in MPM worker mode since a threaded Apache is going to be faster than a pre-fork, besides this server does not have as much memory so it seemed to be better to have multiple threads rather than multiple processes, each of which would have their own memory segment. There however is the issue that this server also needs to run PHP, the accepted method to do so is to use mod_php along with Apache; however PHP is not thread safe.

The alternative is FastCGI, basically it spawns PHP processes as a separate stand-alone process, with their own memory space, much like Apache pre-fork, however now when one process grows to big, or when a process is no longer needed it can be cleaned up, keeping memory to a minimum. Also, FastCGI is perfectly thread safe, this means that with Apache running in MPM worker mode we could now still run our PHP scripts even when they were not thread safe.

Setting up mod_fastcgi is not that hard, it takes some httpd.conf configuration values, and off course the loadmodule is assumed here:

# Set up mod_fastcgi
<IfModule `mod_fastcgi`.c>
    FastCgiIpcDir /var/tmp/fcgi-ipc/
    FastCgiConfig -autoUpdate -singleThreshold 100 -killInterval 300 -idle-timeout 240 -pass-header HTTP_AUTHORIZATION

    AddHandler  fastcgi-script              .fcgi .fcg .fpl

    Action      application/x-httpd-php5    /fastcgi-bin/php5.fcgi
    AddType     application/x-httpd-php5    .php .php5
</IfModule>

# Set up the script alias, basically anything in this directory gets executed as a fastcgi script
ScriptAlias /fastcgi-bin/ "/usr/local/www/fastcgi-bin/"
<Location /fastcgi-bin/>
    Options ExecCGI 
    SetHandler fastcgi-script
    Order allow,deny
    Allow from all
</Location>

Because of errors that mod_fastcgi was throwing out at me, I figured mod_fcgid is worth a try. However all of the configurations I found required me to add an FCGIWrapper line into each of my VirtualHost blocks, which was an immediate no-no since there is well over 100 of those on the server, and I'd rather spend my time doing other things.

With some trial and error, and some Googling I put the following together for mod_fcgid, tested it out, and once it worked perfectly set Apache off running with the following configuration:

# Set up mod_fcgid
<IfModule `mod_fcgid`.c>
    AddHandler  fcgid-script                 .fcgi .fcg .fpl
    IPCCommTimeout 60
    SocketPath  /var/tmp/fcgi-ipc/

    Action      application/x-httpd-php5    /fastcgi-bin/php5.fcgi
    AddType     application/x-httpd-php5    .php .php5
</IfModule>

# Set up the script alias, basically anything in this directory gets executed as a fastcgi script
ScriptAlias /fastcgi-bin/ "/usr/local/www/fastcgi-bin/"
<Location /fastcgi-bin/>
    Options ExecCGI 
    SetHandler fcgid-script
    Order allow,deny
    Allow from all
</Location>

Notice how similar they both are, that was the whole goal. mod_fcgid was supposed to be a drop in replacement, and thankfully it was. PHP was served as it once was, with a twist.

The php5.fcgi script is as follows for those of you trying to set this up as well:

1
2
3
4
5
6
7
#!/bin/sh
# To use your own php.ini, comment the next line and uncomment the following one
#PHPRC="/usr/local/etc"
#export PHPRC
PHP_FCGI_CHILDREN=4
export PHP_FCGI_CHILDREN
exec /usr/local/bin/php-cgi

Some things that that intrigued me is that my php processes seemed to be capped now. Where mod_fastcgi would spawn processes but never remove them or kill them when they were no longer needed, mod_fcgid keeps the running php processes to a sane limit instantly starting new processes when required to handle the requests coming in. This means that the server now has more free memory for MySQL, or file caches which has helped speed up other types of transfers as well. Even with the relatively short time that mod_fcgid has been in place it has been faster, more reliable and more sane than mod_fastcgi. Whichever one you pick, make sure to benchmark your server and check your error logs to solve common issues, the issue may not be what FastCGI module you picked but rather PHP itself that is causing the errors!

PHP 5.2.6 on FreeBSD with extensions

There seems to be an issue in PHP 5.2.6 with extensions where the extensions have to be loaded in a certain order or the PHP interpreter has a tendency to crash and fail miserably. It is weird, as depending on the system configuration the order of the extensions has to be entirely different.

Certain extensions have a dependency on other extensions and their functionality so it is understandable that they are required to be loaded before the other modules can be loaded, however this has never been an issue before. I am not entirely sure how PHP does it, but I am betting it loads all the modules and then runs an init function of sort, and since all the modules are loaded at the same time, there are no issues.

However, in this case it seems that multiple functions are clashing, or something along those lines.

Running PHP in a gdb session showed the following:

Program received signal SIGSEGV, Segmentation fault.
0x00000000 in ?? ()
(gdb) bt
#0  0x00000000 in ?? ()
#1  0x29803544 in __do_global_dtors_aux () from /usr/local/lib/php/20060613/simplexml.so
#2  0x29807c04 in _fini () from /usr/local/lib/php/20060613/simplexml.so
#3  0x2824f558 in tls_dtv_generation () from /libexec/ld-elf.so.1
#4  0x28251558 in ?? () from /libexec/ld-elf.so.1
#5  0xbfbfe9c8 in ?? ()
#6  0x282327b6 in elf_hash () from /libexec/ld-elf.so.1
#7  0x282350a0 in dlclose () from /libexec/ld-elf.so.1
#8  0x081374e4 in zend_hash_apply_deleter ()
#9  0x0813757f in zend_hash_graceful_reverse_destroy ()
#10 0x0812da7c in zend_shutdown ()
#11 0x080f4f67 in php_module_shutdown ()
#12 0x081aa892 in main ()

So it seems that simplexml is the culprit here, lets remove it. Once removed from extensions.ini we re-run gdb and give it the same information we gave it before, this time however we see this:

Cannot load module 'SPL' because required module 'simplexml' is not loaded in Unknown on line 0

My gut instinct is to now move simplexml ABOVE the line that says extension=spl.so. After we do this, PHP is able to run without any issues. Now, for the fun:

extension=spl.so
extension=xml.so
extension=ctype.so
extension=zlib.so
extension=session.so
extension=mbstring.so
extension=pdo.so
extension=pdo_sqlite.so
extension=ftp.so
extension=posix.so
extension=pcre.so
extension=json.so
extension=zip.so
extension=suhosin.so
extension=pdo_mysql.so
extension=bz2.so
extension=openssl.so
extension=gd.so
extension=pdf.so
extension=hash.so
extension=mhash.so
extension=curl.so
extension=mcrypt.so
extension=xmlwriter.so
extension=tokenizer.so
extension=filter.so
extension=simplexml.so
extension=iconv.so
extension=sqlite.so
extension=dom.so
extension=xmlreader.so
extension=gettext.so
extension=tidy.so
extension=mysqli.so
extension=mysql.so
extension=sockets.so
extension=pspell.so
extension=imap.so
extension=ncurses.so
extension=xsl.so

Is the extension list on one of the servers I administrate. Notice that spl.so is at the top of the file, and simplexml.so is somewhere in the middle. And this works perfectly, however the same file on a second server causes it to bomb spectacularly. However, I had kept a backup of the original extensions.ini on the second server, put it back, and then experimented with it, and this is what came out:

extension=simplexml.so
extension=spl.so
extension=suhosin.so
extension=json.so
extension=zip.so
extension=filter.so
extension=hash.so
extension=session.so
extension=sysvmsg.so
extension=gd.so
extension=pdf.so
extension=tidy.so
extension=magickwand.so
extension=odbc.so
extension=calendar.so
extension=sysvshm.so
extension=soap.so
extension=tokenizer.so
extension=mcrypt.so
extension=iconv.so
extension=readline.so
extension=sysvsem.so
extension=bz2.so
extension=zlib.so
extension=ftp.so
extension=pcntl.so
extension=ncurses.so
extension=posix.so
extension=gettext.so
extension=mhash.so
extension=pcre.so
extension=mbstring.so
extension=xmlwriter.so
extension=xml.so
extension=xmlrpc.so
extension=sqlite.so
extension=openssl.so
extension=ctype.so
extension=ming.so
extension=dom.so
extension=xmlreader.so
extension=curl.so
extension=mysqli.so
extension=mysql.so
extension=sockets.so
extension=imap.so
extension=xsl.so

Now when I tried this on the first system where that first configuration file comes from, it bombs spectacularly leaving a core-dump. I have NO IDEA what is going on. I just know that log messages like the following:

pid 54662 (php-cgi), uid 80: exited on signal 11 (core dumped)
pid 54655 (php-cgi), uid 80: exited on signal 11 (core dumped)
pid 54658 (php-cgi), uid 80: exited on signal 11 (core dumped)
pid 54661 (php-cgi), uid 80: exited on signal 11 (core dumped)
pid 54660 (php-cgi), uid 80: exited on signal 11 (core dumped)

are a thing of the past, until next time when I upgrade or re-compile PHP and the order is played with again.

If you have any hints, insights or knowledge of what is going on, and what magic order I have to place the extensions in, please let me know by sending me an email at xistence@gmail. This is going to be a mystery, for now, as I definitely don't have the time to figure out what is going on and why.

addnumbers.asm updated

If you have no clue what I am talking about, check out my previous post. I have fixed the flaws, and now it will parse the commands it is handed on the command line, and stick them into an integer. It will now print it to stdout.

This has been an awesome learning experience for me. Especially with regards to how to do recursion, how to debug a pure assembly program with gdb and whatnot.

Here comes the code, as I said before, see my previous thread if you don't know what I am talking about, as well as instructions on how to compile the program.

; File: addnumbers2.asm
; Bert JW Regeer
; 
; 2008-01-27
; 
; Function:
;   Add numbers together that are provided as arguments to the program in argv[1] and argv[2].
;
; Known limitations:
;   This will hopefully be fixed in the next revision. Floating point numbers will not work.
;   Any input that is larger than an integer will cause overflows, and thus will not work.

section .data

    ; Define some strings that are going to be used throughout the program

    ; This string is to let the user know they failed to provide the proper amount of arguments.
args    db  "Program addnumbers: ", 0xa, 0x9, "addnumbers <number 1> <number 2>", 0xa, 0x9, "Arguments 1 and 2 are required.", 0xa, 0x9, "Anything that will cause addition to overflow an int (2,147,483,647), will fail! :P", 0xa
largs   equ $ - args

    ; This string contains part of the output that we are going to send to the terminal. The last two
    ; bytes will be filled automatically by the program, before it is output to stdout.
msg db  'Answer: ', 0
lmsg    equ $ - msg

num1    dd  0
num2    dd  0

section .bss
    ; This is where I am going to store the output of my conversion from an integer to a char
answer  resb    64

section .text
global start                ; Linker defined entry point. Mac OS X this is start. 
global _start               ; FreeBSD and others _start.

_start:
start:
    push    ebp         ; 
    mov ebp, esp        ; Set up the stack frame

    mov ecx, [ebp + 4]      ; Get argc, we check if it set to at least 3
    mov edx, ebp        ; Put the base pointer into edx, so we can use that in 
                    ; our dereferences coming up
    add edx, 8          ; Add 8. We want to skip ebp and argc

    cmp ecx, 3          ; Check if we have at least 3 arguments to the program. 
                    ; At least two arguments are required, and the 3rd one is 
                    ; the name of the program
    jl  exit            ; If the value in ecx is less than 3, jump to exit

    mov esi, 1          ; Set the index to 1

    mov eax, [edx + esi * 4]    ; Move the pointer to the character array into eax
    push    eax         ; Push eax onto the stack
    push    num1            ; Push the pointer to num1 onto the stack
    call    ctoi            ; Call my char to int function
    add esp, byte 8     ; Put the stack pointer back to where it was.

    inc esi         ; Increase the index

    mov eax, [edx + esi * 4]    ; Move the pointer to the character array into eax
    push    eax         ; Push eax onto the stack
    push    num2            ; Push the pointer to num2 onto the stack
    call    ctoi            ; Call my char to int function
    add esp, byte 8     ; Put the stack pointer back to where it was.

    mov eax, [num1]     ; Move value stored in num1 into eax
    add eax, [num2]     ; Add num2 to eax, this will now be stored in eax

    push    eax         ; Push the new calculated number onto the stack
    call    itoa            ; Convert the integer to a character array

    push    dword lmsg      ; Push the length of the string
    push    msg         ; Push the location of the string in memory
    push    dword 0x1       ; Push the file descriptor to write to
    mov eax,4           ; Move the syscall number into eax
    push    eax         ; Push the syscall onto the stack
    int 0x80            ; Interrupt 80, go to kernel
    add esp, byte 16        ; Clean up the stack

    push    answer          ; Push answer onto the stack
    call    len         ; Get it's length

    push    edi         ; Push the length onto the stack
    push    answer          ; Push the pointer to the character string onto the stack
    push    dword 0x1       ; Push the file descriptor to write to
    mov eax,4           ; Push the syscall number into eax
    push    eax         ; Push the syscall onto the stack
    int 0x80            ; Interrupt 80, go to kernel
    add esp, byte 16        ; Clean up the stack

    jmp done            ; Program is done. Jump to done

exit:
    ; This label is jumped to when we want to exit the program and let the user know how
    ; to run the program. Like for instance what paramaters to send the program.
                    ; Call sys_write
    push    dword largs     ; Push the length of the string
    push    dword args      ; Push the location of the string in memory
    push    dword 0x1       ; Push the file descriptor to write to
    mov eax,4           ; Move the syscall number into eax
    push    eax         ; Push the syscall onto the stack
    int 0x80            ; Interrupt 80, go to kernel
    add esp, byte 16        ; Clean up the stack

done:
    ; This is the label we jump to when we want to exit the program, we set the exit code
    ; to 0.
                    ; Call sys_exit
    push    dword 0x0       ; Push the value to return to the operating system
    mov eax,1           ; Move the syscall number into eax
    push    eax         ; Push the syscall onto the stack
    int 0x80            ; Interrupt 80, go to kernel

    ; We never return to this function, so no need to clean the stack. :P

ctoi:
    ; char to i. We actually convert entire character array's to integers.
    ;
    ; We get two paramaters on the stack. The first one we grab is the pointer to the place to store
    ; the number. The second is the pointer to the character array.

    push    ebp         ; Push the old base pointer onto the stack
    mov ebp, esp        ; Create a new base pointer
    push    esi         ; Store all the original registers
    push    eax
    push    ebx
    push    ecx 
    push    edx         ; Push edx, so that we can overwrite it
    sub esp, 4          ; We get another storage space on the stack
    mov [esp], dword 10     ; This is the number we are going to multiply by

    mov eax, [ebp + 12]     ; Move the pointer to the character array into eax
    push    eax         ; Push the pointer to the character array onto the stack
    call    len         ; Call the string length versoin
    add esp, byte 4     ; Reclaim the space we lost when we pushed eax onto the stack

    mov ebx, [ebp + 8]      ; This is where we are going to store the numbers
    mov esi, [ebp + 12]     ; This is the pointer to the character array
    movzx   ecx, di         ; move with extended zero edi.
    mov edi, 0          ; Clean up edi

    ctoi_loop:
    mov eax, [ebx]      ; Move the value stored in ebx into eax
    mul dword [esp]     ; Move it over a 10s place.
    mov [ebx], eax      ; Move the new number back into ebx

    movzx   eax, byte [esi + edi]   ; Move the character into eax
    movsx   eax, al         ; We just want the lower part of the character

    sub eax, 0x30       ; Subtract 0x30, ASCII 0 so that it is an actual number
    add [ebx], eax      ; Add the new number to the old number that has been multiplied by 10
    inc edi         ; Increase the counter
    loop ctoi_loop          ; Loop into cx is 0

    add esp, byte 4
    pop edx         ; Restore all the registers
    pop ecx
    pop ebx
    pop eax
    pop esi
    mov esp, ebp        ; Make esp the original base pointer again
    pop ebp         ; Pop the original base pointer into the register
    ret             ; Return caller

itoa:
    ; Recursive function. This is going to convert the integer to the character.
    push    ebp         ; Setup a new stack frame
    mov ebp, esp
    push    eax         ; Save the registers
    push    ebx
    push    ecx
    push    edx

    mov eax, [ebp + 8]      ; eax is going to contain the integer
    mov ebx, dword 10       ; This is our "stop" value as well as our value to divide with
    mov ecx, answer     ; Put a pointer to answer into ecx
    push    ebx         ; Push ebx on the field for our "stop" value

    itoa_loop:
    cmp eax, ebx        ; Compare eax, and ebx
    jl  itoa_unroll     ; Jump if eax is less than ebx (which is 10)
    xor edx, edx        ; Clear edx
    div ebx         ; Divide by ebx (10)
    push    edx         ; Push the remainder onto the stack
    jmp itoa_loop       ; Jump back to the top of the loop
    itoa_unroll:            
    add al, 0x30        ; Add 0x30 to the bottom part of eax to make it an ASCII char
    mov [ecx], byte al      ; Move the ASCII char into the memory references by ecx
    inc ecx         ; Increment ecx
    pop eax         ; Pop the next variable from the stack
    cmp eax, ebx        ; Compare if eax is ebx
    jne itoa_unroll     ; If they are not equal, we jump back to the unroll loop
                    ; else we are done, and we execute the next few commands
    mov [ecx], byte 0xa     ; Add a newline character to the end of the character array
    inc ecx         ; Increment ecx
    mov [ecx], byte 0       ; Add a null byte to ecx, so that when we pass it to our
                    ; len function it will properly give us a length

    pop edx         ; Restore registers
    pop ecx
    pop ebx
    pop eax
    mov esp, ebp        
    pop ebp
    ret

len:
    ; Returns the length of a string. The string has to be null terminated. Otherwise this function
    ; will fail miserably. 
    ; Upon return. edi will contain the length of the string.

    push    ebp         ; Save the previous stack pointer. We restore it on return
    mov ebp, esp        ; We setup a new stack frame
    push    eax         ; Save registers we are going to use. edi returns the length of the string
    push    ecx

    mov ecx,  [ebp + 8]     ; Move the pointer to eax; we want an offset of one, to jump over the return address

    mov edi, 0          ; Set the counter to 0. We are going to increment this each loop

    len_loop:           ; Just a quick label to jump to
    movzx   eax, byte [ecx + edi]   ; Move the character to eax.
    movsx   eax, al         ; Move al to eax. al is part of eax.
    inc di          ; Increase di.
    cmp eax, 0          ; Compare eax to 0.
    jnz     len_loop        ; If it is not zero, we jump back to len_loop and repeat.

    dec di          ; Remove one from the count

    pop ecx         ; Restore registers
    pop eax
    mov esp, ebp        ; Set esp back to what ebp used to be.
    pop ebp         ; Restore the stack frame
    ret             ; Return to caller

Introduction to Intel Assembly

This semester I am taking a class on Intel assembly, because I want more of an insight into how the computer works, and it will allow me to better reverse engineer new viruses and spyware. The class is also required if one is a Software Engineering major, so that means I have to take it.

The professor who teaches it absolutely sucks at teaching. He gets up in front of the class and mumbles through some powerpoint slides, which provide no real information, and then goes on and on about his days at Motorolla. It really sucks. Oh, best part is this quote:

"I think that is how Intel processors do it. I don't know I have not read up on it yet"

Well, we had our first assignment. Sum two numbers and then output them to the screen. We were supposed to write inline assembly using Visual Studio C++, but if we are to do an assembly class, then we should learn how to do write assembly, not have some parts assembly and other parts the compiler. Sure it makes it easy as you will get immediate access to the standard C library, but if you want that, you can just link against it.

The following code examples were written on Mac OS X, and will work on FreeBSD. Linux uses a different calling convention for it's syscalls, and as such this code will not run on Linux, unless it is modified. Do note, you need an Intel Mac for this to work. This is Intel assembly.

Compile the code with (Mac OS X):

nasm -f macho addnumbers.asm
ld -o addnumbers addnumbers.o

or (FreeBSD)

nasm -f elf addnumbers.asm
ld -o addnumbers addnumbers.o

Then you can run it with:

./addnumbers 1 5

As you can see in the comments of the source code, there are still some limitations, but the rest of the source code should be made readable by the comments that are provided.

Porting to Linux: Please double check that all the syscall numbers are the same. There are some differences between Linux and FreeBSD/Mac OS X in that regard.

; File: addnumbers.asm
; Bert JW Regeer
; 
; 2008-01-27
; 
; Function:
;   Add numbers together that are provided as arguments to the program in argv[1] and argv[2].
;
; Known limitations:
;   As of right now, the numbers that are provided may not add up to anything more than 9.
;   This will hopefully be fixed in the next revision. Floating point numbers will not work.
;
; Todo:
;   Write conversion routine, to convert a string of numbers into a real integer on which
;   math may be performed.

section .data

    ; Define some strings that are going to be used throughout the program

    ; This string is to let the user know they failed to provide the proper amount of arguments.
args    db  "You failed to provide the proper amount of arguments", 0xa
largs   equ $ - args

    ; This string contains part of the output that we are going to send to the terminal. The last two
    ; bytes will be filled automatically by the program, before it is output to stdout.
msg db  'Answer: ', 0,  0
lmsg    equ $ - msg

section .text
global start                ; Linker defined entry point. Mac OS X this is start. FreeBSD and others _start.
global _start

_start:
start:

; Start the program here.

    add esp, byte 8     ; We don't care about argc or argv[0]

    pop     ecx         ; Get the first argument or argv[1]
    jecxz   exit            ; If there was no argument. Exit. Let the user know why

    ; Change the number from a character to an actual dword
    mov eax, dword [ecx]    ; Move the character into eax so we can manipulate it
    sub eax, 0x30       ; Remove 0x30 from the character. To make it an actual number, not an ASCII number.

    pop ecx         ; get the second argument or argv[2]
    jecxz   exit            ; If there was no second argument. Exit. Let the user know why

    mov ebx, dword [ecx]    ; Move the character into ebx so we can manipulate it
    sub ebx, 0x30       ; Remove 0x30 from the character. To make it an actual number, not an ASCII number.

    add eax, ebx        ; Add the two numbers together
    add eax, 0x30       ; Make it an ASCII number again

    mov [msg+lmsg-2], eax   ; Replace the null character in the msg with the answer
    mov [msg+lmsg-1], dword 0xa ; Add an newline character so that when it spits it out it is neatly formatted

                    ; Call sys_write
    push    dword lmsg      ; Push the length of the string
    push    msg         ; Push the location of the string in memory
    push    dword 0x1       ; Push the file descriptor to write to
    mov eax,4           ; Move the syscall number into eax
    push    eax         ; Push the syscall onto the stack
    int 0x80            ; Interrupt 80, go to kernel
    add esp, byte 16        ; Advance the stack pointer

    jmp done            ; Program is done. Jump to done

exit:
                    ; Call sys_write
    push    dword largs     ; Push the length of the string
    push    dword args      ; Push the location of the string in memory
    push    dword 0x1       ; Push the file descriptor to write to
    mov eax,4           ; Move the syscall number into eax
    push    eax         ; Push the syscall onto the stack
    int 0x80            ; Interrupt 80, go to kernel
    add esp, byte 16        ; Advance esp past the part we were just at

done:
                    ; sys_exit
    push    dword 0x1       ; Push the value to return to the operating system
    mov eax,1           ; Move the syscall number into eax
    push    eax         ; Push the syscall onto the stack
    int 0x80            ; Interrupt 80, go to kernel

    ; We never return to this function, so no need to clean the stack.