Exploring Assembly Lab
Goals
Your primary goal for this lab is to play around with Compiler Explorer to write C code that corresponds to specific assembly structures. Through this lab, you will get practice thinking about how different assembly structures are used to work with different sizes and types of data.
Getting started
Open up Compiler Explorer. Make sure to modify the settings following the Getting Started instructions from HW5, using the -Og
compiler flag.
-
Type the following function in the left-hand text editor:
char f(char x) { return x; }
You should see the following assembly code in the right-hand panel:
f(char): movl %edi, %eax ret
- Now make a small change to the function in C: change it to return an
int
instead of achar
.int f(char x) { return x; }
How does the assembly change?
-
Make one more change to your function – have it take in an
unsigned char
instead of achar
. Again, how does the assembly change? - Finally, play around with the input and output type to try to result in other
movXYZ
commands.
Arrays in assembly
Let’s look at how arrays (or addresses treated as arrays) work in assembly.
- Define a new function that takes in an address of an
int
(to be treated as an array).int g(int *arr) { return arr[2]; }
Take a look at the assembly:
h(int*): movl 8(%rdi), %eax ret
-
What can you change to have it work with
%rax
instead of%eax
? Does this change its use of%rdi
? -
Now try changing the index used. If you change it to
3
what happens to the assembly? Predict the change and then try it out. -
Now try passing an index as a parameter:
int g(int *arr, long i) { return arr[i]; }
Does the resulting assembly match your intuition?
-
Now change the type of the index to an
int
. Does this match your intuition? What if the input index is of typeunsigned int
? -
Finally, try doing a relative index:
int g(int *arr, int i) { arr[i] = arr[i-1]; return arr[i]; }
Structs in assembly
What about structs? Let’s see how that pointer math works out.
- Type this C code:
typedef struct { long a; int b[2]; short c; } my_struct_t; int h(my_struct_t *s) { s->a = 12; s->b[0] = 15; s->b[1] = 0xabcdef01; s->c = 0xFEDC; return 'A'; }
-
Sketch for yourself how the struct is laid out in memory. Focus on the members, not the individual bytes. Can you make sense of the assembly?
-
Now change the code to take in an index to access within the array
b
:int h(my_struct_t *s, unsigned int i) { s->a = 12; s->b[0] = 15; s->b[1] = 0xabcdef01; s->c = 0xFEDC; return s->b[i]; }
How does the assembly change? Is this what you expect?
- What if you change the index to be just an
int
instead of anunsigned int
?
The lea
instruction
Now let’s explore how lea
works.
-
Make one change to your function from before: have it return the address of
s->b[i]
instead of the value stored at that location:int * h(my_struct_t *s, unsigned int i) { s->a = 12; s->b[0] = 15; s->b[1] = 0xabcdef01; s->c = 0xFEDC; return &(s->b[i]); }
What changes in the assembly?
-
Now write a simpler function:
int h2(int a, int b) { return 3*a + b; }
Look at the assembly. What is going on here?
Next steps
Try out more variations of functions. What can you change to result in more types of assembly instructions?
Then, try plugging in your functions from previous assignments. What do you get for the different functions from HW1? What about functions from the Queues assignment? (Don’t forget to copy over the struct definition with any functions you want to try.)
Be creative in your explorations, and ask questions!