CS 208 s21 — Learning Block #11

Table of Contents

1 Review

Take some time to practice translating assembly to C. There's no single correct C code for the given assembly code—multiple valid translations exist.

Exercise 1: long f(long x, long y) 1

f(long, long):
        subq    %rsi, %rdi
        jne     .L3
        movq    %rdi, %rax
        ret
.L3:
        movq    %rsi, %rax
        ret

Exercise 2: long f(long x, long y) 2

f(long, long):
        cmpq    $2, %rdi
        setle   %dl
        cmpq    %rsi, %rdi
        sete    %al
        testb   %al, %dl
        je      .L3
        movl    $1, %eax
        ret
.L3:
        movl    $2, %eax
        ret

Exercise 3: long f(long *p) 3

f(long *p):
        testq   %rdi, %rdi
        je      .L4
        movq    (%rdi), %rax
        leaq    -10(%rax), %rdx
        testq   %rdx, %rdx
        jle     .L3
        addq    %rax, %rax
        ret
.L3:
        addq    $1, %rax
        ret
.L4:
        movl    $0, %eax
        ret

2 Practice

Translate this assembly to C code:4

.LC0:
        .string "Hello %d"
main:
        pushq   %rbx
        movl    $0, %ebx
        jmp     .L2
.L3:
        movl    %ebx, %esi
        movl    $.LC0, %edi
        movl    $0, %eax
        call    printf
        addl    $1, %ebx
.L2:
        cmpl    $9, %ebx
        jle     .L3
        movl    $0, %eax
        popq    %rbx
        ret

CSPP practice problems 3.24 (p. 224), 3.26 (p. 228), and 3.30 (p. 236)

3 Background for Lab 2

See the slides here: ./lab2-background-slides.pdf (includes a walkthrough of the activity below)

4 gdb activity

  • Get started with these commands:
wget http://cs.carleton.edu/faculty/awb/cs208/s21/topics/gdb-activity.tar
tar xvf gdb-activity.tar
cd gdb-activity
make
  • Try running the program with ./gdb-activity, what happens?
  • Open gdb-activity.c
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int compare(int a, int b);

int main(int argc, char** argv)
{
    int a, b, n;
    char input[100];
    printf("enter good args: ");
    if (fgets(input, 100, stdin) == NULL) {
        printf("I said good args\n");
    }

    n = sscanf(input, "%d %d", &a, &b);
    if (n == 2 && compare(a,b) == 1) {
        printf("good args!\n");
    }
    else {
        printf("bad args, try harder!\n");
    }
    return 0;
}

Observations:

  • four functions are called: printf, fgets, sscanf, and compare
    • the first three are C library functions (since they aren't declared anywhere, they must come from the #include of library headers)
    • look each library function up in the terminal with man 3 FUNCTION, or consult cplusplus.com/FUNCTION (the latter is often easier to understand)
  • to get the program to print "good args!", we need fgets to return something other than NULL, have sscanf return 2, and have compare(a, b) return 1
    • fgets will read from the command line (stdin) and store the string in input, up to 100 characters
      • only returns NULL on failure, so we probably don't have to worry about that
    • sscanf is a super useful function: it parses a string (the first parameter) according to a format string given by the second parameter
      • %d is the format specifier for an integer, so this sscanf call will parse input as two integers separated by a space
      • the parsed items (i.e., each %d) will be written to the corresponding pointers provided as arguments after the format string
        • so the first integer in input will be stored in a and the second will be stored in b
    • compare is actually implemented in raw assembly in gdb-activity.s
  • Lets use gdb to get a sense for how everything is fitting together. (This tutorial video goes over using gdb if you want to review.)
  • Running disas compare from within gdb gives
0x00000000004006d7 <+0>:     push   %rbx
0x00000000004006d8 <+1>:     mov    %rdi,%rbx
0x00000000004006db <+4>:     add    $0x5,%rbx
0x00000000004006df <+8>:     add    %rsi,%rbx
0x00000000004006e2 <+11>:    cmp    $0xd0,%rbx
0x00000000004006e9 <+18>:    sete   %al
0x00000000004006ec <+21>:    movzbq %al,%rax
0x00000000004006f0 <+25>:    pop    %rbx
0x00000000004006f1 <+26>:    retq
  • We can reverse engineer the C code to be something like
int compare(int a, int b) {
    return a + b + 5 == 0xd0; // we add %rdi, %rsi, and 5 together in %rbx and then compare it to $0xd0
                              // sete writes 1 to the given register if the cmp indicates the operands are equal
                              // since this is the return value, and we want compare to return 1, 
                              // we should choose inputs to make these equal
}

Footnotes:

1
long f(long x, long y) {
    long z = x - y;
    if (z == 0) {
        return z;
    }
    return y;
}
f(long, long):
        subq    %rsi, %rdi  // compute x - y, ok to overwrite x since we don't use it again
        jne     .L3         // jump to L3 when %rdi != %rsi (i.e., x - y != 0)
        movq    %rdi, %rax  // copy z to the return value
        ret
.L3:
        movq    %rsi, %rax  // copy y to the return value
        ret
2
long f(long x, long y) {
    if (x < 3 && x == y) {
        return 1;
    } else {
        return 2;
    }
}
f(long, long):
        cmpq    $2, %rdi    // perform x - 2, set condition codes
        setle   %dl         // write 1 to the lowest byte of %rdx when x <= 2 (i.e., %dl contains 1 when x < 3)
        cmpq    %rsi, %rdi  // perform x - y, set condition codes
        sete    %al         // write 1 to the lowest byte of %rax when x == y
        testb   %al, %dl    // perform %al & %dl, set condition codes
        je      .L3         // jump to L3 when %al & %dl == 0 (i.e., jump unless both previous set instructions
                            // wrote 1, meaning x < 3 and x == y)
        movl    $1, %eax    // make return value 1
        ret
.L3:
        movl    $2, %eax    // make return value 2
        ret
3
#include <stdlib.h>  // to provide NULL
long f(long *p) {
    if (p == NULL) {
        return 0;
    }
    if (*p - 10 > 0) {
        return *p + *p;
    } else {
        return *p + 1;
    }
}
f(long *p):
        testq   %rdi, %rdi       // perform p & p, set condition codes
        je      .L4              // jump to L4 if p & p == 0 (i.e, jump if p is NULL)
        movq    (%rdi), %rax     // put *p in the return value
        leaq    -10(%rax), %rdx  // put *p - 10 in %rdx (remember lea uses the value in the register, not memory
        testq   %rdx, %rdx       // perform (*p - 10) & (*p - 10), set condition codes
        jle     .L3              // jump to L3 if (*p - 10) & (*p - 10) <= 0 (i.e., don't jump if *p - 10 > 0)
        addq    %rax, %rax       // return value is now *p + *p
        ret
.L3:
        addq    $1, %rax         // add 1 to return value
        ret
.L4:
        movl    $0, %eax         // set return value to 0
        ret
4
#include <stdio.h>

int main() {
    for (int i = 0; i < 10; i++) {
        printf("Hello %d", i);
    }
}