Created
February 3, 2017 23:13
-
-
Save tbodt/1898fecdb64d929c23ac1c321ffadf54 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Ooohhh! A vending machine! | |
# This is a time vending machine. You can buy timepieces. You pay by waiting. | |
#################################### Data segment #################################### | |
# All sorts of data. | |
.data | |
welcome_message: | |
.int 30 | |
.ascii "Time vending/killing machine\n\n" | |
seconds_prompt: | |
.int 9 | |
.ascii "Seconds: " | |
minutes_prompt: | |
.int 9 | |
.ascii "Minutes: " | |
hours_prompt: | |
.int 7 | |
.ascii "Hours: " | |
volunteered_1: | |
.int 21 | |
.ascii "You have volunteered " | |
volunteered_2: | |
.int 33 | |
.ascii " seconds of your precious time.\n\n" | |
dot_space: | |
.int 2 | |
.ascii ". " | |
space_lparen: | |
.int 2 | |
.ascii " (" | |
space_seconds_rparen_newline: | |
.int 10 | |
.ascii " seconds)\n" | |
oscillator_description: | |
.int 13 | |
.ascii "An oscillator" | |
metronome_description: | |
.int 11 | |
.ascii "A metronome" | |
sundial_description: | |
.int 9 | |
.ascii "A sundial" | |
clock_description: | |
.int 7 | |
.ascii "A clock" | |
watch_description: | |
.int 7 | |
.ascii "A watch" | |
digital_watch_description: | |
.int 15 | |
.ascii "A digital watch" | |
# Format: Description address, then price in seconds. | |
items: | |
.long oscillator_description | |
.int 1 | |
.long metronome_description | |
.int 2 | |
.long sundial_description | |
.int 10 | |
.long clock_description | |
.int 30 | |
.long watch_description | |
.int 60 | |
.long digital_watch_description | |
.int 3600 | |
items_count: | |
.int 6 | |
what_to_buy_prompt: | |
.int 25 | |
.ascii "What do you want to buy? " | |
thank_you: | |
.int 60 | |
.ascii "\nThank you for buying something from this vending machine.\n\n" | |
your_change_1: | |
.int 15 | |
.ascii "Your change is " | |
your_change_2: | |
.int 18 | |
.ascii " seconds. That's:\n" | |
second_s: | |
.int 11 | |
.ascii " second(s)\n" | |
minute_s: | |
.int 11 | |
.ascii " minute(s)\n" | |
hour_s: | |
.int 9 | |
.ascii " hour(s)\n" | |
#################################### Macros #################################### | |
# Simplify calling the subroutines. They have to be up here so they can be defined before they are used. | |
.macro write message | |
push \message | |
call write | |
add $4, %esp | |
.endm | |
.macro write_int number | |
push \number | |
call write_int | |
add $4, %esp | |
.endm | |
.macro read_int_prompt prompt | |
push \prompt | |
call read_int_prompt | |
add $4, %esp | |
.endm | |
.text | |
.global _start | |
################################### Actual code #################################### | |
_start: | |
write $welcome_message | |
# %ebx is how many seconds is in the machine | |
read_int_prompt $seconds_prompt | |
mov %eax, %ebx | |
read_int_prompt $minutes_prompt | |
mov $0, %edx # prepare to multpily | |
mov $60, %ecx # multiply by 60 | |
mul %ecx | |
add %eax, %ebx # seconds += minutes * 60 | |
read_int_prompt $hours_prompt | |
mov $0, %edx # like above | |
mov $3600, %ecx | |
mul %ecx | |
add %eax, %ebx # seconds += hours * 3,600 | |
write $volunteered_1 | |
write_int %ebx | |
write $volunteered_2 | |
mov $1, %ecx # %ecx is counter, going from 1 to items_count | |
mov $items, %edx # %edx is current item adress, with 8 (size of item structure) added each time | |
items_loop: | |
write_int %ecx | |
write $dot_space | |
write 0(%edx) # name address is first field | |
write $space_lparen | |
write_int 4(%edx) # price is second field | |
write $space_seconds_rparen_newline | |
inc %ecx | |
add $8, %edx | |
cmp items_count, %ecx | |
jle items_loop # if %ecx <= items_count, continue loop | |
read_int_prompt $what_to_buy_prompt | |
dec %eax | |
mov $items, %ecx # only for the purposes of the next line | |
mov 4(%ecx, %eax, 8), %eax # find out the price, using complicated addressing | |
# price is in %eax | |
sub %eax, %ebx # charge them for their purchase | |
call sleep_for_eax_seconds | |
write $thank_you | |
write $your_change_1 | |
write_int %ebx | |
write $your_change_2 | |
mov %ebx, %eax # can only divide by %eax | |
mov $60, %ebx # will divide by 60 several times | |
mov $0, %edx | |
div %ebx # told you so... | |
write_int %edx | |
write $second_s | |
mov $0, %edx | |
div %ebx # really, just dividing by 60, printing the remainder, and repeating with the quotient | |
write_int %edx | |
write $minute_s | |
mov $0, %edx | |
div %ebx # just like in write_int, except with 60 and not 10 | |
write_int %edx | |
write $hour_s # we're actually printing the number in Babylonian-friendly base 60 | |
# exit | |
mov $1, %eax # exit syscall number | |
mov $0, %ebx # exit status | |
int $0x80 | |
################################### Subroutines ################################### | |
# Print something, quick and easy. | |
# Push the address of the output buffer. | |
write: | |
# Stack frames are cool! | |
push %ebp | |
mov %esp, %ebp | |
# no need for local variables | |
pushal # pusha only pushes first 16 bits of register | |
mov 8(%ebp), %edi # put the address in a register | |
mov $4, %eax # write syscall number | |
mov $1, %ebx # stdout | |
lea 4(%edi), %ecx # address | |
mov (%edi), %edx # count | |
int $0x80 | |
# clean up the stack frame | |
popal # reverse pushal | |
mov %ebp, %esp | |
pop %ebp | |
ret | |
# I copied these from the ascii-int.asm file, so I could change things I didn't like. | |
# Write an integer to standard output. Push it on the stack first. | |
write_int: | |
# This is a stack frame. | |
push %ebp | |
mov %esp, %ebp | |
push $0 # -4(%ebp): output buffer | |
pushal # push all registers | |
mov $0, %edi # %edi is a counter | |
mov 8(%ebp), %eax # put number to write in %eax | |
# Divide by 10. Convert the remainder to a character and push it, then repeat with the quotient. | |
write_int_loop: | |
mov $0, %edx # prepare to divide | |
mov $10, %ebx # must divide by a register | |
div %ebx # ready, set, divide! | |
add $48, %edx # convert integer to digit | |
push %edx # a stack is needed to put the characters in the right order | |
inc %edi # increment counter | |
cmp $0, %eax # stop when all the digits have been eaten | |
jne write_int_loop | |
# Pop the right number of characters from the stack and output them. | |
write_int_output: | |
cmp $0, %edi #Loop until we popped everything we pushed | |
je write_int_done | |
pop %edx | |
movb %dl, -4(%ebp) #Put the popped character into our temp variable | |
mov $4, %eax #Linux/UNIX command for Write | |
mov $1, %ebx #Linux/UNIX to output to the screen | |
lea -4(%ebp), %ecx #Linux/UNIX needs an address. This will (L)oad the (E)ffective (A)ddress of the local variable and put it in ECX | |
mov $1, %edx #Output just one character | |
int $0x80 | |
sub $1, %edi | |
jmp write_int_output #Next loop | |
write_int_done: | |
popal # pop all registers | |
# clean up stack frame | |
mov %ebp, %esp | |
pop %ebp | |
ret | |
# Read an integer from the keyboard. Returns it in %eax. That's it. | |
read_int: | |
# This is a stack frame. | |
push %ebp | |
mov %esp, %ebp | |
push $0 # -4(%ebp): input buffer | |
push $0 # -8(%ebp): running total | |
push $0 # -12(%ebp): whether to ignore rest of line, using C booleans | |
push %ebx | |
push %ecx | |
push %edx | |
push %edi | |
read_int_loop: | |
mov $3, %eax # read syscall | |
mov $0, %ebx # stdin | |
lea -4(%ebp), %ecx # address (look, it's the address of the input buffer) | |
mov $1, %edx # read just one character | |
int $0x80 | |
mov $0, %ecx # zero the top 24 bits | |
movb -4(%ebp), %cl # put the character in the bottom 8 | |
cmp $10, %cl # if the character is a newline, we're done | |
je read_int_done | |
cmp $1, -12(%ebp) # if ignoring the rest of the line, ignore this character, and the others will be ignored | |
je read_int_loop | |
cmp $48, %cl # If the character is less than '0', | |
jl read_int_invalid | |
cmp $57, %cl # or the character is greater than '9', | |
jg read_int_invalid # do something about it. | |
sub $48, %cl # Convert digit to integer | |
# Multiply the running total by 10, and then add the digit. That's the only real algorithm here. | |
mov -8(%ebp), %eax # put running total into %eax | |
mov $0, %edx # clear %edx to prepare for multiplication | |
mov $10, %ebx # can only multiply by a register | |
mul %ebx # ready, set, multiply! | |
add %ecx, %eax # add the digit on | |
mov %eax, -8(%ebp) # put this back in the local variable | |
jmp read_int_loop | |
read_int_invalid: | |
movl $1, -12(%ebp) # an invalid character means ignore the rest of the line | |
jmp read_int_loop | |
read_int_done: | |
pop %edi | |
pop %edx | |
pop %ecx | |
pop %ebx | |
mov -8(%ebp), %eax # return the running total in %eax | |
# clean up stack frame | |
mov %ebp, %esp | |
pop %ebp | |
ret | |
# My own subroutine. Prints a prompt (from the stack), then reads an int. | |
read_int_prompt: | |
push %ebp | |
mov %esp, %ebp | |
push 8(%ebp) # the prompt | |
call write | |
call read_int | |
mov %ebp, %esp | |
pop %ebp | |
ret | |
# This is the fun part of assembly programming. | |
sleep_for_eax_seconds: | |
# Actually, this is pretty complicated. The Linux call to sleep is nanosleep, which | |
# takes two pointers to structures as arguments. Luckily, the second one can be NULL. | |
# Unluckily, the first can't be NULL. I'll build one of these on the stack. | |
push %ebp | |
mov %esp, %ebp | |
pushal # basically all the registers need to be preserved | |
sub $8, %esp # make room for a structure | |
mov %eax, (%esp) # put seconds in first field | |
movl $0, 4(%esp) # zero second field | |
mov $162, %eax # nanosleep system call number | |
mov %esp, %ebx # address of the structure | |
mov $0, %ecx # second parameter is NULL | |
int $0x80 # let's hope it works... | |
add $8, %esp | |
popal | |
mov %ebp, %esp | |
pop %ebp | |
ret | |
# Tell vim that this is a GNU assembler file | |
/* vim: ft=gas : | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment