0% found this document useful (0 votes)
5 views11 pages

Assembly Memory Management Techniques

The document discusses the use of memory in assembly programming, emphasizing the role of registers, pointers, and memory addressing. It explains the differences between pointers and references, pointer arithmetic in C, and how to manage data segments in assembly code. Additionally, it covers array manipulation, relative addressing, and the importance of position independence in modern executables.

Uploaded by

Smit Sanghvi
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views11 pages

Assembly Memory Management Techniques

The document discusses the use of memory in assembly programming, emphasizing the role of registers, pointers, and memory addressing. It explains the differences between pointers and references, pointer arithmetic in C, and how to manage data segments in assembly code. Additionally, it covers array manipulation, relative addressing, and the importance of position independence in modern executables.

Uploaded by

Smit Sanghvi
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

Assembly Using Memory

Registers are the fastest storing locations in a CPU, but they are limited in number. So, programs
need to use the main memory RAM to store larger data like arrays, structures, etc.
Memory can be addressed. An address is basically the index into the “array” of bytes: each byte
has a unique address. The basic operations which can be done on memory are read and write.
Let’s say there is a byte at address n in memory. If I wanted to, I could think of bytes n to n+7
as a 64-bit signed integer value. But hardware does not care what we think the values are. It will
just store and follow the instructions.

Pointers
A pointer is just a number representing a memory address. For eg, in C:

Hiding actual pointers away from the programmer prevents pointer arithmetic: you can't move a
few bytes over a see what's there. You just must follow the reference to a nicely managed data
structure. Pointers let you get into trouble: add/subtract and have a look at other memory or
follow the pointer and treat it as a different type of data. References protect programmers from
that danger.
Difference Between a Pointer and a Reference:
They are otherwise conceptually similar: both let you refer to somewhere in memory with a small
value (the memory address). e.g. if you have a huge object in memory, you can pass a pointer or
reference to a function. That's cheaper than copying the whole object and still lets the function
see it. But (in most languages), it would also let the function modify the object.

Pointer Arithmetic in C
When we add or subtract from a pointer in C, the compiler automatically multiplies the number
you add/subtract by the size of the type the pointer points to.
For eg: Adding 1 to an int64_t* moves the pointer 8 bytes forward, not 1 byte.
The Reason: In C, pointers are typed, compiler knows what kind of data they point to.
Conclusion: This is why pointer arithmetic looks like it’s adding small numbers, but it actually
jumps over whole data items, not bytes.
Case in Assembly: Assembly isn't going to do us the same favour. In assembly, a pointer is just
an integer that we imagine representing a memory address.

Assembly Code Segments


.text: Tells the assembler/linker that this is the code section.
.data: The data segment is used to store values that are stored in the executable file and
initialized when the program starts, i.e. initialized static memory.
Labels are used for the same thing in .data as in .text: to give a name to a memory address.
In Assembly: In C:
This assembly is rough equivalent to the above C code. The .quad directive tells the assembler “I
want a quad-word (64-bits) of memory holding this value” (but nothing about signed/unsigned:
that's for us to remember).
.fill: This can be used to create an array-like memory space. You can give a repeat, size, and
initial value.

it is a directive that can be used inside the .data or .bss


segment.
This creates a data structure like array with a label “array” to give name to a memory address.

.bss: This segment can be used to specify uninitialized static memory. e.g. this creates an array
of 100 8-byte values that will not be initialized.

Reserves required amount of RAM, which is 100*8 = 800 bytes and does
not store in the executable.

Equivalent C:

.bss vs .data
Arrays and Memory
An array is just a sequence of values of a specific type (and therefore size) that are adjacent in
memory.
In C:

This would give an array of size 10 which stores 8 bytes integer values. In
C, array variables are effectively just pointers to the start of the array.
In assembly:

10 * 8 = 80 bytes. This should do something like as done in C. But we must


remember to “add $80, %rsp”, before any “ret”.

Working with Heap

Alternative of Using Static Memory

We can initialize or not initialize, our choice.


Ok now how to use this memory? To use this memory, we must refer to the memory location
with the label we mentioned it by name. We use the “call” which needs a memory address.
To use the static memory, we refer to the memory address by using the label we gave:

Now because we will be often working with memory addresses of data: pointers. The “lea”
instruction (Load Effective Address) can be used to get a pointer to something in memory: “lea”
is analogous to the “&” operator in C.

Here is some analogy with C for understanding:

Working With Arrays


We can use “lea” to work with arrays. We start with putting the address of the
th
start of the array which is array element at the 0 index.

We have an “array” of 64-bit integers (8 bytes for each element). So, we can get from element 0
to element 1 by moving 8 bytes over.

Accessing the nth element in an Array


Because we know that the elements in the array are 64-bit integers (8 bytes for each element)
which means all of them are spaced by 8 bytes.
1. Hence by an increment of 8 bytes we can traverse the array.
Let’s say we want the element at index 2 in the array.

This is a very common way of calculating the memory address every time. But we can do
better
2. The (address) operand can have more parts. We have been giving the address, we can
also give a second value, which is the index which indicates how far to move from the
given address (first argument).
Now to traverse through the array, we can simply loop with rcx+=8 every time for 64-bit
integers, until desired index has been reached.
3. We can still do better. I do not want to increment 8 every time, instead I want to loop i++
directly till I have reached my desired index. This can be done by the third component
scale. It can take only 1, 2, 4 or 8 as argument. This is what the second
argument(index) gets multiplied by.

Now rcx can be incremented by 1 every time and it will get multiplied by 8, giving us the
memory location we want.
Here is the full code:

4.

Example:
In C: In C++:

We are discussing the C implementation C++ version is just for understanding.


We have a struct called “pair” with two 32-bit integers (4-bytes). In total “pair” is 8-byte type
where the first 4-bytes are for “a” (offset of 0) and the next 4 bytes are for "b” (offset of 4).
pairs is a pointer to the first pair object.
Assume the struct is already implemented in assembly. Below is the example of using all the
address operand arguments:
The i==7 iteration of that loop would effectively be:

We are using movl as we are working with 32-bit


integers.
movl $10, (%rbx, %rcx, 8) performs placing value 10 at (rbx + rcx*8) = (rbx + 7*8) for i=rcx=7.
This is for “a”. (offset of 0).
movl $11, 4(%rbx, %rcx, 8) performs placing value 11 at [4 + (rbx + rcx*8)] = [4 + (rbx + 7*8)]
for i=rcx=7. This is for “b”. (offset of 4).

IMP: The address and index MUST be a register, the displacement a literal integer,
and scale can only be among (1,2,4,8).

Relative Addressing

Here we are moving the value stored at address arr into the register %rdi. This
This will work fine when using the ld linker directly because it generated non – Position
Independent Executable (PIE). But when compiled and linked with gcc(modern default), we’ll get
an error like:

The reason is that modern executables by default are built as PIE – Position Independent
Executables. This means that the program can be loaded anywhere in memory each time it runs.
So static data like arr won’t have a fixed absolute address.

Position Independence is useful for shared libraries, and for address space layout randomization
which is a way to mitigate security problems around memory accesses.

Solution:
Instead of encoding absolute addresses, we use addresses relative to the
instruction pointer (RIP).
Every time the code runs, we have a different instruction pointer. We then calculate the
the offset:
Offset = (address of arr) – (address of instruction pointer(rip))
We add this offset as displacement to get to address of “arr”. Doing that we can work with
static memory (.data and .bss segments) in a way that works everywhere.

The RIP-relative addressing is not relevant for call x instructions where the assembler is doing
something more complex and figures it all out. Otherwise, we will probably want to use x(%rip)
instead.

The above table discussed previously updates to the one below after Relative addressing:

Local Stack Array


Let us put an array of size n all with 64-bit integers on stack. We will then add them all up from
index 0 to n-1:
Let’s use %r15 to hold the argument n value from %rdi. %r15 is call preserved, so we have to
preserver the caller’s value. We will also use %rbx, and preserve that too.
we can just subtract from %rsp to get ourselves enough stack space for bytes that we'll think of
as the array.
Assembly Syntax: AT&T syntax and Intel syntax

You might also like