Print an unsigned integer (and about the BSS segment)

The next thing we need to do is printing an integer. More precisely, an unsigned integer as this obviously makes sense when we compute the factorial. As before we first develop a standalone component that just does this job.

Assignment: quiz04

Write a program printuint.s that prints the unsigned value stored in its data segment at label n. So a skeleton for this program is given by

1
2
3
4
5
6
7
8
9
    .data
n   .byte 123

    .text

    /*
        Your code that prints the unsigned byte at label n

    */

On theon submit your program with the following command

1
submit hpc quiz04 printuint.s

Using the ULM tools

Recall from session 5 that the command line tools (ulm, ulmas) only run on theon. The debugger ulm-qui and ulm-qui-random-memory only work on heim (and for ssh you then have to use option flag -X).

Video tutorial

The problem that you have to solve is similar to reading an integer. Basically your just doing things the way around. Recall, for reading an integer you have to read single characters, compute their numerical value and “attach the digits” to assemble the integer value. Now, for printing the integer you have to print the value digit by digit.

Part one: printing the digits in reverse order (because that is simple)

Let's say \(n\) is an unsigned integer with \(k\) decimal digits, i.e.

\[n = (d_{k-1}, \dots, d_0)_{10}:= \sum\limits_{i=0}^{k-1} d_i \cdot 10^i\]

then in a first step we compute the digits (in sequential order) by

\[\begin{array}{lcrlcr}d_0 & \leftarrow & n \bmod 10,\; &n & \leftarrow & \left\lfloor \frac{n}{10} \right\rfloor \\d_1 & \leftarrow & n \bmod 10,\; &n & \leftarrow & \lfloor \frac{n}{10} \rfloor \\ & \vdots & & & \vdots & \\d_{k-1} & \leftarrow & n \bmod 10,\; &n & \leftarrow & \left\lfloor \frac{n}{10} \right\rfloor \\\end{array}\]

And even if we don't know the number of digits it is no problem. We just stop when \(n\) becomes zero. So an algorithm that prints all digits is simple

1
2
3
4
5
do {
    d <- n mod 10
    n <- n div 10
    print digit d
} while n > 0

For the (integer) division the ULM provides the two instructions divq and idivq. Both compute the quotient and the remainder. So this makes it even simpler.

There is only one problem: It would print the digits in reverse order, i.e. \(d_0 \dots d_{k-1}\) instead of \(d_{k-1} \dots d_0\). But anyway, havin that is a good starting point. And there are already a few things to do just to get that far.

And here the video about that

Part two: printing the digits in the right order

For bringing the digits into the right order, we can first write the to memory instead of printing them immediately. Once all digits were generated (and written to memory) we read them back in reverse order and print them.

Before you start to figure out the technical details (e.g. what instructions are needed) first think about it a bit more general:

  • We need at most 20 memory cells for the digits. That is because the maximum number of decimal digits of a 64-bit unsigned integer is given by \(\left\lceil \frac{64 \cdot \log 2}{\log 10} \right\rceil \). And we need an extra byte to mark the end of the string.

    That means we need 21 memory cells that can be used to store the characters for the digits. Initially these cells will be zero initialized. For referring to these cells we also need to know the address of the first memory cell. Let's use a label buf for that:

    Note that a register %P is used to store a pointer to the first memory cell.

  • Before we write the first digit to memory we increment the pointer. So we have

  • Say that the first digit is 3 then we would write its ASCII value to the address %P:

    The same way we proceed for the remaining digits.

  • When all digits were computed and the corresponding ASCII value written to memory we want that register %P points to the first digit that gets printed. So for an integer \(n=123\) we want the following situation:

For writing a byte from a register use the instruction movb. The instruction copies the least significant byte from a register to a specified address in memory. For example

1
    movb    %1, (%2)

copies \(\text{val} := u(\%1) \bmod 2^8\) to the memory cell with address \(u(\%2)\):

So where in memory can we write the 21 bytes? We could use the data segment. For example, with

1
2
3
4
5
    .data
buf .byte 0
    .byte 0
    ...
    .byte 0

the executable would contain a data segment with 21 zero initialized bytes (if we did not miscount when writing the .byte directives). But there is an easier way. An executable can contain beside a text and data segment a so called BSS segment. When an executable gets loaded into memory the program loaded copies the text and data segment to memory and in addition zero initializes a memory region as specified in the BSS segment.

In the assembly program we can describe your BSS segment as follows:

1
2
    .bss
buf .space 21       # 21 bytes

Here the video about that:

More about the BSS segment

In the video the BSS segment was introduced for zero initialized data. Consider the following program to see how this is realized:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    .data
LabelA:
    .byte   42

    .bss
LabelB:
    .space 4
LabelC:
    .space 4

    .text
LabelD:
    halt    0

Now translate it

theon$ ulmas -o ex1 ex1.s
theon$ 

and look at the assembler output:

#TEXT 4
0x0000000000000000: 09 00 00 00 #       halt 0
#DATA 1
0x0000000000000004: 2A #        .byte 42
#BSS 1 8
#SYMTAB
d LabelA                      0x0000000000000000
b LabelB                      0x0000000000000000
b LabelC                      0x0000000000000004
t LabelD                      0x0000000000000000

You see that there is a new section for the BSS segment:

  • The header has the format BSS <alignment> <size>. Unlike the text segment the are now subsequent lines for the BSS segment with data. The <size> and <alignment> attributes is all information the loader needs to initialize the segment. Because the alignment is 1 the BSS segment will start right after the data segment.

  • Also note that the symbol table contains the symbols LabelB and LabelC of type bss.

When you load this with ulm-qui-random-memory you will see the following memory layout:

From the description of the sections you can interpret this as follows: