x86-64 assembly from the ground up

Capstone: summing an array

A table of numbers, a loop that adds them, a division that splits the total into digits, and one write that prints the answer. Every earlier chapter has a line in it.

Part 12 of 12

Pro22 min readBack to Courses

The shape of the program

The data section holds a table of four double words and a message prepared with two empty slots in it. The two zeros in that message are not text: they are places reserved for the digits the program is about to calculate.

The code runs in three steps. A loop walks the table and keeps a running total in eax, counting down from four so the same instruction can add each number in turn. A single division then splits the total into a tens digit and a ones digit. Two stores put those digits into the message, and one write sends the finished line out.

Where the chapters land

The table and the brackets come from the memory chapter, and the four-byte width of each entry from the sizes chapter. The countdown is the loops chapter, and loop is the compact form it mentions: one line to subtract from rcx and jump back while it is not zero.

The division is the arithmetic chapter and the digits are the chapter on turning numbers into text, in its shortest honest version — a two-digit split with no loop, because the total here is known to be small. The two reserved slots are a small piece of the sections chapter, and the write and exit calls are the first ones you learned.

What a real one adds

Real code makes three of these choices carefully. It keeps the total in a wider register so a long table cannot overflow it, it converts with the loop from the digits chapter so any number of digits prints, and it keeps the divisor in a full register with a zeroed high half so a total above 255 is not a fault waiting to happen.

Those are extensions of this program rather than replacements for it. Read the four lines that add, the four that divide and store, and the five that print: the whole shape of a program that computes something and says so is here.

The two halves that matter

_start:
  xor eax, eax          ; the running total starts at zero
  mov rcx, 4            ; four numbers to add
.sum:
  add eax, [nums + rcx * 4 - 4]
  loop .sum             ; count down, one dword at a time

  mov cl, 10
  div cl                ; al is the tens, ah is the ones

Sum a table and print the result

Run it: twelve plus thirty plus five plus eight is fifty-five, and the message has the answer inside it. Then change the numbers, keeping the total below two hundred and fifty-six, and predict the digits before you run it again.

Output
Click "Run" to execute your code

The split uses an eight-bit division, so the total has to stay below 256. Push the numbers higher and the high half of ax is no longer zero, the division faults, and the program dies without printing. For a bigger total, put ten in ecx, clear edx and use div ecx.

Width is a promise the program makes

The capstone chooses eight-bit division because the total is known to be small. That choice is a promise: the quotient has to fit in al and the remainder in ah. If the value is too large for those widths, the processor raises a division fault instead of returning a truncated answer.

A real table would not make that promise. It would keep the total in a wider register, test the range, and use a division whose width matches. The program would be a few lines longer and would keep working when the data changed.

The same reasoning applies to the table entries. A table of double words is four bytes per entry, so the index has to count in fours. Declare the table with dw and every address calculation changes; the loop does not know and will happily read the wrong bytes if you forget.

Testing without a debugger

The output is the only window into this program, so the way to test it is to predict a line before running. Change one number, work out the total by hand, and compare. A mismatch tells you which stage is wrong: the loop, the division or the stores.

Boundary values are worth more than ordinary ones. Test a total of zero, a total with a zero in the middle such as one hundred and five, and the largest total the chosen division can take. Each one exercises a different edge of the same code.

Finally, test the promise itself. Add a number that pushes the total past two hundred and fifty-five and watch the run fail. Knowing what the failure looks like is how you recognise it later.

Three capstone traps. A total above 255 breaks the eight-bit division and kills the run. An index stepped by one instead of four reads the wrong bytes. And a syscall inside the summing loop clobbers rcx, so a counter kept there for loop would stop at the wrong place.

Try these next

  • Add a fifth number and a fifth loop pass, then predict the new total before running.
  • Change the table to words, widen each entry with movzx, and step the offset by two.
  • Replace the two-digit split with the digit loop from the previous chapter so a total above 255 prints.

Sum a table of words and print three digits

The table holds four word-sized entries and the total is 163. Run it, then add a fifth number and change `count` to five. The loop follows the count, and the digit loop follows however large the total becomes.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

The table holds double words. What does the index have to be multiplied by to reach each one?