AArch64 Playground
4.8 · Printing a variable from .data

Printing a variable from .data

In many assembly programs, data is stored in variables in the .data section before it is used. Printing an immediate, a constant written into the instruction itself, is straightforward. Printing a value stored in memory takes an extra step, because the value has to be loaded into a register first.

This lesson loads a value out of .data into a register, passes it to printf, and then changes the value in memory.

A review of printing with printf

Printing in assembly always follows the same four steps:

  1. Define the string or format string: the text to print, with a specifier such as %d marking where each value goes.
  2. Load the address of the string or format string into x0.
  3. Place each value to be printed in the next argument register (w1 or x1, then w2 or x2, and so on), if there are any.
  4. Call printf.

These steps stay the same when printing variables stored in the .data section. The difference is in step 3.

In previous lessons, the values being printed were immediates, moved straight into an argument register. For example:

mov w1, 67

Here the value comes out of a .data variable instead of an immediate, but it still ends up in the same argument register.

Loading values from .data

Suppose we have an integer value of 85 stored in a variable named importantNumber in the .data section, as shown below:

.dataimportantNumber:      .word 85              // a 4-byte integer

To load the value stored in importantNumber, we must first get the address of the variable. Once we have the address, we can use it to load the value stored at that memory location. The following two lines do both:

        ldr     x9, =importantNumber        // x9 = the address of the variable        ldr     w1, [x9]                    // w1 = the value stored at that address

The ldr instruction is used both to get the address of the variable and to load the value stored at that address, so two ldr instructions are needed.

The first ldr puts the address of the variable in register x9. The = in front of the label asks for the label's address rather than what is stored there, and an address is 64 bits, so it always goes in an x register. The second ldr uses this address to reach memory and loads the value found there into register w1. A .word is 32 bits, so the load uses the 32-bit w form of the register; an x1 load would drag in the 4 bytes stored after the variable too. The register x0 is reserved for the address of the format string, which is passed to printf as the first argument.

x9 is a scratch register: a function you call, such as printf, is allowed to change it. That is fine here, because the address is needed only until the value is loaded.

note

The square brackets [] in the ldr instruction indicate memory access (dereferencing). They tell the processor to use the value inside the register as a memory address and access the data stored at that location.

Printing the variable

Once the format string address is in x0 and the value is in w1, printf can be called to display the output. The complete program below prints Integer Variable Content: 85.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 1try it: run it, or step one instruction at a timeOpen in playground

note

Notice the .balign 4 line between the string and the word. .balign 4 adds zero bytes until the next address is a multiple of 4. The string takes 30 bytes (29 characters plus the zero byte that marks its end), so without the padding the word would start 30 bytes into .data, which is not a multiple of 4. A 4-byte value belongs at an address divisible by 4, and .balign 4 makes sure of that however long the string above it is.

Changing a variable in memory

str (store register) is the mirror image of ldr: it copies a register into memory at the address in the square brackets. Loading a variable gives you a copy in a register, and changing the copy leaves the variable in memory as it was. To change the variable itself, store the new value back to its address.

The program below loads a counter that starts at 41, adds 1 to the copy, stores the copy back, and then loads the counter again. It prints visits before: 41, after: 42. Delete the str line and run it again: the second number stays 41, because only the register copy changed.

loading editor...

regfile

N clearZ clearC clearV clear

x0–x30 are the integer registers.

X0arg00x0000000000000000
X1arg10x0000000000000000
X2arg20x0000000000000000
X3arg30x0000000000000000
X4arg40x0000000000000000
X5arg50x0000000000000000
X6arg60x0000000000000000
X7arg70x0000000000000000
X8ind0x0000000000000000
X90x0000000000000000
X100x0000000000000000
X110x0000000000000000
X120x0000000000000000
X130x0000000000000000
X140x0000000000000000
X150x0000000000000000
X16ip00x0000000000000000
X17ip10x0000000000000000
X18pr0x0000000000000000
X190x0000000000000000
X200x0000000000000000
X210x0000000000000000
X220x0000000000000000
X230x0000000000000000
X240x0000000000000000
X250x0000000000000000
X260x0000000000000000
X270x0000000000000000
X280x0000000000000000
X29fp0x0000000000000000
X30lr0x0000000000000000
SP0x0000000080000000
PC0x0000000000400000
console

Output prints here as your program runs.

Press step or run under the editor, or feed stdin from the box below.

not assembled

example 2try it: run it, or step one instruction at a timeOpen in playground

note

You can print variables of any data type using the process shown above. Use the format specifier and the register width that match the variable's type.

When calling printf, you must pass arguments whose data types match their corresponding format specifiers. A mismatch prints garbage.

pitfall

A common mistake from this lesson, with a broken program and its fix that you can run:

Check yourself

  1. Why does printing a .data variable take two ldr instructions instead of one?
  2. count is a .word printed with %d. Should it be loaded into w1 or x1, and why?
  3. After ldr w10, [x9] and add w10, w10, 5, has the variable at the address in x9 changed? Which instruction would change it?

answers

show answers
  1. The first ldr gets the variable's address, and the second loads the value stored at that address.
  2. w1: a .word is 4 bytes and %d prints a 32-bit int, while an x1 load would pull in the 4 bytes after the variable as well.
  3. No, only the copy in w10 changed. str w10, [x9] writes it back.

Practice