This project will feel somewhat familiar in that it is nearly identical to the preceding project: there is a coding problem and a puzzle-solving problem. The major change is that everything is at the assembly level:
Working with assembly will get you a much more acquainted with the low-level details of the x86-64 platform and give you a greater appreciation for "high-level" languages (like C).
Download the code pack linked at the top of the page. Unzip this which will create a project folder. Create new files in this folder. Ultimately you will re-zip this folder to submit it.
Makefile
clock.h
clock_main.c
clock_sim.c
clock_update_asm.s
clock_update.c
test_clock_update.c
clock_update_asm.c
test_clock_update.org
test_hybrid.org
testy
bomb_download.sh
bombNN.zip
bombNN/
unzip bombNN.zip
bombNN/bomb.c
main()
bomb
bombNN/bomb
bombNN/README
input.txt
The functions in this problem are identical to a previous project in which code to support an LCD clock display was written. These functions are:
int set_tod_from_ports(tod_t *tod)
TIME_OF_DAY_PORT
tod
int set_display_from_tod(tod_t tod, int *display)
tod_t
display
int clock_update()
CLOCK_DISPLAY_PORT
The big change in this iteration will be thatthe functions must be written in x86-64 assembly code. As C functions each of these is short, up to 85 lines maximum. The assembly versions will be somewhat longer as each C line typically needs 1-4 lines of assembly code to implement fully. Coding these functions in assembly give you real experience writing working assembly code and working with it in combination with C.
The code setup and tests are mostly identicalfor this problem as for the previous C version of the problem.Refer to original Clock LCD Display Problem descriptionfor a broad overview of the simulator and files associated with it.
As discussed in class, one can generate assembly code from C code with appropriate compiler flags. This can be useful for getting oriented and as a beginning to the code your assembly versions of the functions. However, this exercise is about writing assembly yourself to gain a deeper understanding of it.
Code that is clearly compiler-generated with no hand coding will receive 0 credit.
Do not let that dissuade you from looking at compiler-generated assembly code from you C solution to the functions. Make sure that you take the following steps which are part of the manual inspection criteria.
The files to be submitted for this problem include
Graders may examine these for a correspondence between to the algorithm used in the C version to the Assembly version. Compiler generated assembly often does significant re-arrangements of assembly code with many intermediate labels that hand-written code will not have.
If you were not able to complete the C functions for the Project 2 or were not confident in your solutions,see a course staff member who will help you get them up and running quickly.
Comment your assembly code A LOT. While good C code can be quite self-explanatory with descriptive variable names and clear control structures, assembly is rarely so easy to understand. Include clear commentary on your assembly. This should include
.ROUNDING_UP
While it is a slow instruction that is cumbersome to set up, usingidivXdivision instruction is the most human-readable means to compute several results needed in the required functions. Compiler generated code uses many tricks to avoid integer division so a lack ofidivXinstructions along this line will be a clear sign little effort has been put into the assembly code.
idivX
Be Careful with constants: forgetting a$in constants will lead to a bare, absolute memory address which will likely segfault your program. Contrast:
$
movq $0,%rax # rax = 0 movq 0, %rax # rax = *(0): segfault # bare 0 is memory address 0 - out of bounds
Running your programs, assembly code included, in Valgrind can help to identify these problems. In Valgrind output, look for a line number in the assembly code which has absolute memory addresses or a register that has an invalid address.
Recognize that in x86-64 function parameters are passed in registers for up to 6 arguments. These are arranged as follows
rdi / edi / di
rsi / esi / si
rdx / edx / dx
rcx / ecx / cx
r8 / r8d / r8w
r9 / r9d / r9w
and the specific register corresponds to how argument sizes (64 bit args inrdi, 32 bit inedi, etc). The functions you will write have few arguments so they will all be in registers.
rdi
edi
Use registers sparingly. The following registers (64-bit names) are "scratch" registers or "caller save." Functions may alter them freely (though some may contain function arguments).
rax rcx rdx rdi rsi r8 r9 r10 r11 # Caller save registers
No special actions need to be taken at the end of the function regarding these registers except thatraxshould contain the function return value.
rax
Remaining registers are "callee save": if used, their original values must be restored before returning from the function.
rbx rbp r12 r13 r14 r15 # Callee save registers
This is typically done by pushing the callee registers to be used on the stack, using them, them popping them off the stack in reverse order. Avoid this if you can (and you probably can in our case).
pushX/popX
subq/addq
rsp
For reference, here is a picture that appears in the lecture slides that summarizes the names and special uses for the registers in x86-64.
Figure 1:Summary of general purpose register usages in x86-64.
Below is a rough outline of the structure of required assmebly file. Consider copying this file as you get started and commenting parts of it out as needed.
.text # IMPORTANT: subsequent stuff is executable .global set_tod_from_ports ## ENTRY POINT FOR REQUIRED FUNCTION set_tod_from_ports: ## assembly instructions here ## a useful technique for this problem movX SOME_GLOBAL_VAR(%rip), %reg # load global variable into register # use movl / movq / movw / movb # and appropriately sized destination register ### Data area associated with the next function .data # IMPORTANT: use .data directive for data section my_int: # declare location an single int .int 1234 # value 1234 other_int: # declare another accessible via name 'other_int' .int 0b0101 # binary value as per C '0b' convention my_array: # declare multiple ints in a row .int 10 # for an array. Each are spaced .int 20 # 4 bytes from each other .int 30 .text # IMPORTANT: switch back to executable code after .data section .global set_display_from_tod ## ENTRY POINT FOR REQUIRED FUNCTION set_display_from_tod: ## assembly instructions here ## two useful techniques for this problem movl my_int(%rip),%eax # load my_int into register eax leaq my_array(%rip),%rdx # load pointer to beginning of my_array into rdx .text .global clock_update ## ENTRY POINT FOR REQUIRED FUNCTION clock_update: ## assembly instructions here
set_tod_from_ports()
int set_tod_from_ports(tod_t *tod); // Reads the time of day from the TIME_OF_DAY_PORT global variable. If // the port's value is invalid (negative or larger than 16 times the // number of seconds in a day) does nothing to tod and returns 1 to // indicate an error. Otherwise, this function uses the port value to // calculate the number of seconds from start of day (port value is // 16*number of seconds from midnight). Rounds seconds up if there at // least 8/16 have passed. Uses shifts and masks for this calculation // to be efficient. Then uses division on the seconds since the // begining of the day to calculate the time of day broken into hours, // minutes, seconds, and sets the AM/PM designation with 1 for AM and // 2 for PM. By the end, all fields of the `tod` struct are filled in // and 0 is returned for success. // // CONSTRAINT: Uses only integer operations. No floating point // operations are used as the target machine does not have a FPU.
Note that this function uses atod_tstruct which is inclock.hdescribed here:
// Breaks time down into 12-hour format typedef struct{ int day_secs; // seconds from start of day short time_secs; // seconds in current hour short time_mins; // minutes in current hour short time_hours; // current hour of day char ampm; // 1 for am, 2 for pm } tod_t;
set_tod_from_ports
eax
int
To access a global variable and copy it into a register, use the following assembly syntax
movl TIME_OF_DAY_PORT(%rip), %ecx # copy global var to reg ecx
The function should not changeTIME_OF_DAY_PORTso copying it to a register is among the first steps to perform.
time_secs
idivl
edx
cqto
divisor
quotient
A pointer to atod_tstruct can access its fields using the following offset table which assume that%regholds a pointer to the struct (substitute an actual register name).
%reg
tod->day_secs
movl $5, 0(%reg)
tod->time_secs
movw $5, 4(%reg)
tod->time_mins
movw $5, 6(%reg)
tod->time_hours
movw $5, 8(%reg)
tod->ampm
movb $5,10(%reg)
You will need to use these offsets to set the fields of the struct near the end of the routine.
set_display_from_tod
int set_display_from_tod(tod_t tod, int *display); // Accepts a tod and alters the bits in the int pointed at by display // to reflect how the LCD clock should appear. If any time_** fields // of tod are negative or too large (e.g. bigger than 12 for hours, // bigger than 59 for min/sec) or if the AM/PM is not 1 or 2, no // change is made to display and 1 is returned to indicate an // error. The display pattern is constructed via shifting bit patterns // representing digits and using logical operations to combine them. // May make use of an array of bit masks corresponding to the pattern // for each digit of the clock to make the task easier. Returns 0 to // indicate success. This function DOES NOT modify any global // variables
%rdi
%rsi
%rdx
The packedtod_tstruct is spread across two registers,%rdiand%rsiso will have the following layout.
tod.day_secs
tod.time_secs
tod.time_mins
tod.time_hours
tod.ampm
To access individual fields of the struct, you will need to do shifting and masking to extract the values from the%rdi / %rsiregisters.
%rdi / %rsi
As was the case in the C version of the problem, it is useful to create a table of bit masks corresponding to the bits that should be set for each clock digit (e.g. digit "1" has bit patter0b0000110). In assembly this is easiest to do by using a data section with successive integers. An example of how this can be done is below.
0b0000110
.section .data array: # an array of 3 ints .int 200 # array[0] = 200 .int 300 # array[1] = 300 .int 400 # array[3] = 400 const: .int 17 # special constant .section .text .globl func func: leaq array(%rip),%r8 # r8 points to array, rip used to enable relocation movq $2,%r9 # r9 = 2, index into array movl (%r8,%r9,4),%r10d # r10d = array[2], note 32-bit movl and dest reg movl const(%rip),%r11d # r11d = 17 (const), rip used to enable relocation
Adapt this example to create a table of useful bit masks for digits. The GCC assembler understands binary constants specified with the0b0011011style syntax.
0b0011011
clock_update
int clock_update(); // Examines the TIME_OF_DAY_PORT global variable to determine hour, // minute, and am/pm. Sets the global variable CLOCK_DISPLAY_PORT bits // to show the proper time. If TIME_OF_DAY_PORT appears to be in error // (to large/small) makes no change to CLOCK_DISPLAY_PORT and returns 1 // to indicate an error. Otherwise returns 0 to indicate success. // // Makes use of the previous two functions: set_tod_from_ports() and // set_display_from_tod(). // // CONSTRAINT: Does not allocate any heap memory as malloc() is NOT // available on the target microcontroller. Uses stack and global // memory only.
Call the two previous functions to create the struct and manipulate the bits of an the display. Calling a function requires that the stack be aligned to 16-bytes; there is always an 8-byte quantity on the stack (previous value of therspstack pointer). This means the stack must be extended with apushqinstruction before any calls. A typical sequence is
pushq
subq $8,%rsp # extend the stack by 8 bytes call some_func # stack aligned, call function ## return val from func in rax or eax addq $8,Rsp # restore the stack ## NOTE: the above provides 8 bytes on the stack for local ## variables; in the likely event that more space in stack is ## needed, a bigger stack extension should be done but the stack ## should always be grown by 16*N+8 bytes if calling another ## function as this ensures proper alignment.
If several function calls will be made, a single stack extension is all that is needed. needed as in the below
subq $8,%rsp # extend once call some_func1 # stack aligned, call function ## return val from func in rax or eax ## do some more stuff call some_func2 # stack aligned, call function ## return val from func in rax or eax addq $8,%rsp # restore the stack
16*N+8
N
set_display_from_tod()
movq
make test-prob1
test_clock_update
clock_main
.NEG_PORT
.L32
rbx rbp r12 r13 r14 r15
set_display_from_clock()
.data
clock_update()
subq
addq
NOTE: Passing all tests and earning all manual inspection criteria will earn up to10 Points of Project Makeup Creditwhich will offset past and future loss of credit on projects.
Available only on Lab Machines or Vole
./bomb_downlad.sh
More details on these are described in subsequent sections.
The nature of this problem is similar to the previous project'spuzzlebox: there is a program calledbombwhich expects certain inputs from a parameter file or typed as input. If the inputs are "correct", a phase will be "defused" earning points and allowing access to a subsequent phases. The major change is that thebombprogram is in binary so must be debugged in assembly.
puzzlebox
Below is a summary of useful information concerning the binary bomb.
gdb
The binary bomb makes frequent contact with a scoring server so you can only run it on a list of prescribed machines. These comprise most of the valid CSELabs machines and are listed in the table below.
Attempting to run a bomb on an un-authorized machine will error out immediately as in
> ./bomb Initialization error: illegal host 'ck-laptop'. Legal hosts are as follows: csel-apollo csel-atlas csel-vole-01 csel-vole-02 ...
Download your bomb by running the provided script
p3-code>> ./bomb_download.sh
or by visiting the following web address
http://bomblab.cselabs.umn.edu:15213/
The bomb will download as a.zipfile. On Unix machines, extract the contents using the commandunzipas in
.zip
unzip
> ls bomb0013.zip > unzip bomb13.zip bomb0013/README bomb0013/bomb.c bomb0013/bomb > ls bomb0013.zip bomb0013/ > cd bomb0013 > ls bomb* bomb.c README
README
*
Create a fileinput.txt. The bomb can be run with it as in
> ./bomb input.txt
but you'll likely want to do this ingdbto avoid exploding the bomb.
Scoring is done according to the following table.
Explosion Penalty: 0.5 points are deducted for each explosion up to 10 explosions. 5pt bonus is reduced for every 2 explosions.
On successfully defusing stages, the bomb will contact a server which tracks scores by number. The scoreboard is here:
You'll need to know your bomb number to see your score but can also see the scores of others.
It is possible to download multiple bombs but this will NOT reset your explosion count. Quite the opposite: the default scoring system for the server uses the following conventions.
Since more bombs likely means more explosions, you are strongly advised to download a single bomb and work with it.
If you accidentally run the bomb from the command line, you can kill it with the Unix interrupt key sequenceCtrl-c(hold control, press C key).
Ctrl-c
> ./bomb Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day! ... >
Most of the time you should run the bomb ingdbas in
> gdb ./bomb
Refer to theQuick Guide to GDBif you have forgotten how to usegdband pay particular attention to the sections on debugging assembly.
objdump
Already registered? Login
Not Account? Sign up
Enter your email address to reset your password
Back to Login? Click here