Hello, this is Dr. Miller, and this
is Episode eleven point six of
Assembly.
Today, we're going to talk about
memory offsets and then debugging
and disassembly.
Memory offsets.
So when we have registers,
as we've noted before, Lowden's
store, are how we get
data from memory into
a register.
And so the.
Assembler can only load a bite
and then do some
shifting on on that bite,
it can't do math operations
and there are only certain values
that can actually be loaded.
And so here's this from the
specification.
So if we have a move operation,
it can only have values
from zero to
four FS, so we can only
move up to two bytes into a register
given an immediate.
And so if we have a 32 bit value
that we want to load into a
register, we actually have to do it
through multiple operations.
So we basically have to load and
offset that will then allow it to
read more data than
a sixteen bit number.
And this is because each arm
instruction is thirty two bits long,
and so therefore there is no way for
you to load a 32 bit
number when you have to have part of
the instruction has to be the op
code to say what data operation
am I going to do.
And so when we do that, when we
went to low to register from memory,
we basically have to
sort of jump off of a location
that has where we want to get that
data from.
And so, for example,
if we do a low register,
our zero and then we put an equal
sign and then this number here,
it'll actually generate some code
saying there's going to be some data
here and that data is going to be
zero one one, two, two, three,
three, four, four.
And then you notice that it's it's
actually converted this from Lord
Register of the Equals sign
to it has a PC and then A
it has a negative there,
but it's not really negative rate,
zero additional bytes.
And this is because in
the instruction pointer actually
points a couple of bytes ahead.
And so it is actually
pointing to bytes ahead.
And so that is this location down
here. So it's that offset zero from
the program counter and the program
counter actually points to here when
it's executing this instruction.
So it's a little bit odd, but that's
the way the assembly works now,
it'll be a little bit easier to see
in our next example.
So here we have several bites we
defined. So we got our half words
and our words.
So half of it is 16 bits
and a forward is 32 bits.
And we can see here inside of the
code that we're loading our zero
gets the value of a well, it's going
to jump down here at the end to an
offset that points to a
or an offset that points to B
or C or D,
and then the words, these are going
to take up two bites
and these words will take up four
bites. So we'll see those
differences in the addresses that
get generated.
So what I've done here is I've taken
the original code and then compared
it to the disassembly, the
disassembly I got from
using object dumps, object
dump and then the minus two is
disassembly. And then this is the
binary that we want.
And then it generates a lot of
source code. And so I use
grep to filter it and only look
for the text main with a bracket.
And so that will give me all of the
code that is in here, which is why
this is highlighted.
And then a 30 means print 30 lines
after you find that match.
And so we can see here that
the disassembly figures out what
this offset is.
So says Main plus Xerox
to see for both of these.
But if you look at the program
counter, again, it's relative to the
program counter.
So this is twenty eight bytes away.
Is this offset down here?
And if we take to see if we take one
four seven zero and add to see
we end up with one zero four nine C,
and so that is this location down
here.
And then these are going to be
pointers to the actual memory
addresses, and so when it does
a load register is it is
bouncing down here and getting this
value and copying it in.
And we can see that the first one
was a word. And so we see that it
takes up addresses C
and D, and then the next one
takes up E and F, and
then the next one, which
I think is C, it takes up
zero one, two and three because
it's four bytes and this one will
take out four or five, six and
seven.
And so we can see how these these
addresses keep going down
and these two are the same right
main plus to see, but
the is the program counter
is relative. It's pointing eight by
eight bytes forward for
the program counter.
And then in order to another
way to visualize this, so in the
previous example, I used object
dump, but additionally, there
are professional tools that can
allow you to basically look at the
code.
And so when I compiled my code,
I have debugging symbols in.
And so the disassembly actually
knows the names of the variables
that I used.
If we were to remove those debugging
symbols, that wouldn't actually have
a name here.
And we just have this data offset,
and again, we can see that it
automatically goes and calculates
that for us and says, OK, this is
data 104 and I can see
this one is also the same.
But again, those are relative
program counters.
And then we can see the same thing
in Eita Pro.
So Binary and India is a fairly
inexpensive one.
Either pro is it's a much more
expensive dissembler that you
can purchase for commercial
purposes.
But again, it shows us what we
writing code versus what we actually
get from the binary.
And so it's going to, again, have
these these relative offsets in
their.
All right, debugging, so
a lot of the times will probably
basically want to use the same
debugging tools on
the Raspberry Pi that would be used
on regular Linux.
So, for example, when I go into bug
something, I'll make sure that I
install GEF
and GDB should be installed.
One issue that I've run into is
that Jegede beat is not pick
up on the transition from regular
mode to thumb mode.
And we'll talk about that mode.
And so our debugger has a bug in it.
So as long as you're not doing that,
the bug works in the same way.
And additionally, we have programs
like estrus, and this will
work on Zaidee six, too, but it'll
trace system calls and signals
and we'll see some examples here.
So, for example, all of the system
calls related to
just printing off the number 42,
we can see that it's it's
calling all of these system calls
that allow us to
have our user name.
We're running the exact command,
which is what the show will do.
And then it gets down to the system
call of. Right.
And we'll see that that prints off
the number 42.
But there's a lot of information
that can be gained by that.
If you have a binary that you don't
understand and you're trying to look
at the assembly for it.
And then with GDB, we're going to,
again, have the same commands
and we'll notice that in, again,
GEF is installed here.
We can see that it lists the
registers and so you can
see the regular name for these,
so are zeroth are 12,
are 13 is our stack pointer.
So it points to the stack are 14,
is the link register.
AR 15 is the program
counter.
And so we can see all of those.
And then the typical names that
they're given, you can see the value
that it has and then you can see,
well if that's a pointer, what is a
point to. So for example, when you
start a program are zero
is how many arguments you have
are one is a listing
of those and then are two is
the pointer to the environment.
And so our one has gotten changed
here in the last instruction that I
executed.
You can also see exactly where you
are as far as the stack.
So we have a pointer to the stack
and then our disassembly
showing which line is the next line
that's going to get executed.
And again, it'll show things like
program counter and then
number twenty Ximenes plus twenty
from that location is where it's
loading that data from.
And so you can see all of that in
the disassembly, just as we
had before, some additional
commands. So if you are
interested in virtual memory, you
can use the map to see
what memory addresses are are mapped
to our binary,
what libraries are loaded, you can
use X Files.
And then there's also a process
memory map, and that's the info
proc map, the spaces
in between. And so those are some
commands you can run once you've
started a process that's running.
You can get information about the
virtual memory map and the loaded
files and the process memory.
Additionally, we can do the decisive
a function and will show us all of
the assembly for a particular
function that we might have
defined or we might want to look at.
And then if you're starting off and
you have a C program and
you want to figure out exactly how
it works, well, you can
generate a listing for that
C program.
And so basically, this will show you
the high level code and then what
low level code gets created
and you can see, for example, we got
some locations here that are defined
and those are probably not as
useful. But you can see commands
like, you know, push these registers
and then move this register into
this register and eventually
a branch of the link for print F.
Right. It ends up getting called
because we're printing off something
and then these registers get set
are zero r one or two.
And so the benefit of this
is it allows you to instead of
trying to reinvent the wheel, how do
I call printf or how do I call this?
If you know how to do it in a high
level language, then
you can reverse engineer and figure
out how you would do it in raw
assembly.
And so it kind of gives you a little
link between what the high level
code be involved with some low level
code B.
All right, so today in our lecture,
we talked about memory offsets and
then some debugging and disassembly
commands.
In the future, we're going to talk
about saving registers and an armory
indexing.
So why can't armload a 32
bit address?
Because there is no room in a for by
top code to load a four byte memory
address.
If you have questions, you can email
me Miller, MJ at Kincaide Edu
and you can find me on Twitter at
Milhouse 30.