assembly language is a low level programming
language for a computer or other programmable device that is closest to the machine language.
It is often specific to a particular computer architecture. So there are multiple types
of assembly languages arm is an increasingly popular assembly language, Scott Cosentino teaches
this course about assembly programming with arm. Scott is a popular technical instructor
and author. It is estimated that over 200 billion devices contain an ARM chip, making
the arm language valuable to understand. By understanding the arm assembly language,
programmers can work at a lower level, allowing them to write code that interacts
with hardware in an efficient manner. By the end of this course, you will have a
fundamental understanding of the ARM processor, as well as assembly programming in general, you will
be able to write basic arm assembly programs using various instructions available in the processor. I
encourage you to share the most interesting thing you learn in the comments for the benefit of other
campers who are watching this course. Hello, and welcome. I'm excited today to be presenting to you
an introduction to the arm assembly language. The main goal of this course is really to give you an
introduction to the ideas of low level programming through assembly languages. As well as working
with the arm programming language. In general. Arm is a programming language that's used in
a variety of different applications. You see it in a lot of different embedded devices. And
really popular devices such as different phones, a lot of Android devices and some iPhone devices
are running ARM based chips. And we're also starting to see them become a lot more used.
And a lot of laptops like the new MacBooks are actually using ARM based processors. And because
of this, it's become a really important skill to actually understand how ARM processors work and
how you're actually able to program directly with them. This is because working at a lower level
lets you be closer to the hardware. So lets you write better interactions between the hardware
and the software. Now let's see more efficiently interact between hardware in any situations
where you might need to do something like this. Generally, this course is going to give you
a good familiarity with the ideas of arm, including some of the basic instructions that arm
has the different ways that arithmetic works and different operations such as logical operators
and shift operators that are commonly used in multiple assembly languages. We'll take a look
at different ways that we can branch as well as the loop inside of assembly languages. And then
finally, we'll finish off by taking a look at some hardware interactions, as well as the different
ways that we can use and troubleshoot arm assembly in a Linux based system. Now I'm going to
primarily be using an emulator known as CPU later, as it's very easy for you to follow along with
this on the actual web browser that you have. So for the most part, you want to install any sort
of special equipment in order to make this work. I mean approach here is to make sure that you can be
able to interact with this course and follow along easily without any sort of special prerequisites.
Now the last few videos here are going to be Linux based. So you will need some Linux based machine
or virtual device to be able to follow along with those. But overall, even if you just follow along
with the emulator ones, you'll come away from this course with a very strong understanding of arm
as well as assembly languages. So I hope that this course is valuable for you. And I hope that
you enjoy it. If you enjoy it, please feel free to leave a comment and let me know. And if you
have any sort of questions, also let me know and I'm happy to answer anything that may come up. So
in this video, I'm going to spend some time just discussing the general idea of our architecture of
our ARM processor, as well as getting familiarized a little bit with the development environment that
I'm going to be using in this set of videos, which is actually the CPU later emulator. This emulator
is available online for free, so you can access it from pretty much any computer. And instead
of this emulator, we have a lot of different different architectures that exist for us.
The one that we're mainly interested in is arm V seven. If you're familiar with MIPS
or any other assembly language like that, you'll see that those emulators are also available
for you. But generally we're going to be working in an arm V seven architecture. And
specifically in this set of videos, I'm going to demonstrate the arm V seven D one
soc. So this will be the one that will utilize throughout this series of videos. So I'll
press go and we'll head into the emulator. Just to note if you're following along, you
can follow along through the emulator here or you can follow along on your own computer.
If you have an ARM processor of some sort. Any V seven processor you'll be able to follow
along fine with even earlier ones you'll probably be able to follow along relatively well. The main
goal of this video is not just to teach you arm based assembly, but also to teach you the general
principles of assembly so that you can easily adapt to any other assembly language things like
x86, other versions of arm and so on like that. So in general, the principles that you learn
And throughout this series of videos are going to apply to really any assembly language, with
the syntax specifically being targeted towards ARM processors. So when we get into our emulator,
there are a few things that we have available to us. But there's actually like a lot of things on
the screen that we have available to us. And I'm going to walk you through some of the essential
pieces that we need, just to get started. So the first idea that I'm going to introduce to
you is the idea of registers, which we see over here on the left hand side. Registers are
areas in memory that are very close to the CPU, so they can be accessed quickly, and they can be
written too quickly. So the general storage is going to be relatively fast. However, we have
a limited amount of things that we can store inside of these registers. You'll notice that
there's eight zeros inside of this register. Each zero represents a single hexadecimal value.
Now, if you know about hexadecimal, you'll know that hexadecimal is going to represent four bits.
So each hex value represents four bits of data. Since there's eight hex values, each one
represents four bits, we have a total of 32 bits that we can work with. This is because this gen
processor is a 32 bit processor. So we're going to be working in 32 bits. And that's the constraint
of how much we can store in a single register is 32 bits worth of data will often refer to 32
bits as a word in terms of the size. So if you ever hear me refer to as you know, changing a word
worth of data, I'm referring to 32 bits of data, the idea of a word transitions to other assembly
languages as well. And it generally represents the total like max size of data that can be stored
in a register. So for instance, if you were in a 64 bit processor, a word would be 64 bits in size.
And then we have the concept of a half word, which is half the size of a word. So in 32 bits, a half
word is 16 bits, because it's half of 32 and 64 bits, that half word is 32 bits, because it's half
of 64. And then of course, we have a byte, which is always eight bits. And then we have a single
bit of data. So those are sort of the different sizes that we have available to us. So in general,
when we're working in assembly, we want to try to use these registers as much as possible. So all of
these registers are going to be available to us, with the exception that some of them are
going to have special purposes associated with them that we'll discuss. As we start
to see those special purposes later on. In general, I can say that registers are zero to
our six our general purpose, we can use them for whatever storage we'd like to use them for our
seven is going to have a special functionality to us related to system calls. Essentially,
when we when we are working with assembly, sometimes we need to talk to the operating
system. And we need to ask it for maybe a resource or we need to ask it to terminate or
program when we you know, call to the operating system to ask it to do something for us, it
needs to know what we're asking it to do. And the way that we communicate that is by storing
a value in R seven, and that value will be some numerical value, that it will map in a table to
some some specifically action. So for instance, if I store the value one and R seven, and then I
interrupt the program, the operating system will read that and interpret it as enter the program.
So that's an example of how we might use that. So that's one example of a special
purpose register that we have. Now there are other special purpose registers
that are actually labeled in this emulator. SP, LR and PC. SP is a really interesting
one, which is related to the stack pointer. And that actually introduces us to our next
important concept, which is the concept of stuck memory. If you head over to the memory section
here, you'll see that you have a whole bunch of memory available to us. And this is referred to as
stuck memory. Stuck memory is typically stored in the RAM of the computer. And essentially, it's
slower to access and slower to write to. However, we can store a lot more data in RAM comparative
to data in registers, you'll typically see stack memory used when we want to represent more complex
sets of data. Think of something like a list of numbers. For instance, if I representing a list
of numbers, I could give each location and memory a specific number. And then I can iterate through
those memory locations to get those numbers that would be an example of when we might use stuck
memory. So the SP register is always going to be telling us the address of the next available
piece of memory on the stack. So in this case, you see it's pointing to all zeros, which means
that we are sitting at this location here. Now, in order to determine like the actual
locations, we can think a little bit about like where each address is, so this one is zero. We can
ask what is the address next to this? It's always going to be for larger than the previous. So this
is at address zero, this would be at address four. This would be At address eight, this would be at
address 12. And then we land here at address 16. Now, you might look at this and say, well, it says
one zeros in that 10. Remember that we're working in hex one, zero, and hex is 16 in decimal. So
just keep that in mind that you're always working in hexadecimal. So that's just something to keep
in mind there. And that's the way that our stock memory grows. And you'll get really familiar with
stock memory. We're going to work with it a lot throughout this series of videos. So you'll get a
really good understanding of this as you continue to work with it. The LR register is another
interesting one, it's known as our link register. And the best way to describe this is if you've
ever worked in a higher level language before, you'll know that when you have a function, a
function has a return. And that return allows us to move back to the location of what called the
function. That's what the link register stores, it stores the location that a function should
return back to. So we'll see that when we talk about functions in future videos. And then
finally, the PC is our program counter. What this does is it keeps track of the
location of the next instruction to execute. So all of our instructions are stored in
memory, like all the different things that we're asking the program to do. And the program
counter allows us to move through piece by piece and determine where the next instruction is going
to be located. So those are three really important registers to keep in mind. And like I said, some
of these other registers have special purposes as well. And we'll discuss those as they become
irrelevant to us as we continue on through this. The one final thing that we'll discuss here is
our CPSR register, and our SPS, our register. Our CPSR register is a special type of register that
is used to store information about our program. So an example of some of the information that it
might start, if you subtract two numbers from each other, you might end up with a negative number.
Now, if you remember, in binary, when we want to represent negative numbers, we need to use what's
known as two's complement, two's complement, essentially, you know, it goes through some
conversion mechanism. In order to convert us into that format, we have to do the ones
complement, and then we do the two's complement, and we end up with some number that represents
a negative number. The issue is that that number could also be a positive number. So it's both
positive and negative. But we need to know whether or not it's representing like a negative
number or sister with the large positive number, that's where CPSR register comes in, what it
will do is it will set a flag in memory to say the result of the previous operation was negative,
we can then take a look at that register and say, if the result was negative, interpret this as
a negative number, otherwise interpreted as a positive number. That's sort of the purpose of
the CPSR register, it has a whole bunch of flags sort of similar to that, that tell us special
information about the operations, you know, if a result was negative, for a result was zero, if
a result, how to carry or an overflow, those are the sorts of ideas that we would typically see. So
we'll see those again as we continue on learning about the different operations. So this gives
you a general overview of some of the important components that we need to start programming in
arm assembly. And in the next video, we'll take a look at how to create a really basic assembly
program. So that's what we'll take a look at next. So in this video, we're just going to take
a look at a really basic arm application just to get a really good fundamental feel
of the structure of our programs, as well as the way that they actually run. So we're gonna go
over a lot of the important concepts of you know, where the starting point of our application is,
where the ending point of our application is, and how can we do all of the things in between. So
starting off with this idea, let's discuss the starting point of our program, you'll see that
our emulator automatically puts in two lines for us. Now, if you're programming outside
of the emulator, you won't have these two lines added by default, you have to add them
yourself. But they are essential because they tell us the starting point of our application.
So we break down the lines one by one here. I'm going to start backwards with this underscore
start portion here. This is known as a label, a label is sort of synonymous with a
function in higher level languages. It's a way of being able to divide out segments of
code, such that if you go to the label, you start to execute the code that is underneath that label.
The idea of this is that we set up a start label, which then we are able to say okay, go to the
start and start running this stuff that's there. That's the idea of what we're looking to do.
So that's why we define our start label. And then our global underscore start here
is a way of being able to tell people about this dirt label. So someone is going
to be running our program. So at some point something is going to be running our program,
it needs to know where the starting point is, as well. We define labels by D faults, they aren't
available to anything external to our program. The dot global is telling everyone else about
our start label. So if anyone ever approaches our program and says I want to run you, where's
the start, the start is exported via this global, which means that everything is able to see
where the starting point is, therefore, things are able to go to the starting point to start
executing our program. So this is the general idea of sort of like our main starting point of
our application. So everything that we write is going to be underneath this start label. So that's
where we're going to start writing our code. And the code that I'm going to write is going to
be very, very simple. All it's going to do is it's going to move some data into registers zero, that
way, you can see how we move data into registers. And then I'm going to move some data into
register seven, it might seem a little bit weird that I'm jumping straight to register
seven. The reason being is because register seven is a special purpose register. What it
does is it stores information about system calls. When we go to the operating system, we often want
to ask it to do specific things for us, right, the operating system manages input and manages
output, it manages the execution of programs. So if we need the operating system to do something
for us, we need some method of communicating with it. This method of communication comes in two
pieces. The first is in system interrupts. And the second is in system called numbers. So we placed a
special number into register seven. And that tells the operating system what we would like it to do.
So basically, we placed the number to ourself, and then we call an interrupt the interrupts
go to the operating system, and it says, Hey, we need something done, the operating system
comes in, and it reads register seven, which has some number inside of it, it takes that number
and it compares it to like a lookup table, and it says, Okay, well that number corresponds with
this task, and then it completes that task for us. In our case, our task is very simple. We want
our program to end. So if I want to terminate the execution of my program, what I do is I move
the number one into our seven, and then I call an interrupt the operating system then goes to
register seven, it says, Oh, I see the number one here, it goes to it's a little table and it's
table says one corresponds to exit the program, and then it terminates the program for us. So
that shows us how we end our program. So let's take a look at how that's actually written. So
the first thing I want to do is move some data into our zero. That way we can see just generally
how we can move data into registers and what that looks like during execution. So the way that we
do this is as follows, we're going to start by writing in what's called an opcode, we'll use it
set in a number of different ways, either op code operation, pneumonic is also used, I'm going to
refer to them as operations or op codes. So we're gonna start by running in the operation that
corresponds to moving data. And that is m o v. Now I use capital letters for mine, you don't have
to do that you can write it in lowercase as well, I just prefer this convention. And this is the one
that I'm most most comfortable with. So this is the way that I usually end up writing it. So the
way that our move operation actually works is it moves data into locations, right. Specifically,
we're going to move data into registers. So what we need to do is we need to provide it
with two things, we need to give it a destination for our data, as well as a source for our data.
So the source is where we're getting the data from the destination is where the data is ending
up. The destination is the first argument given to this operation. And then the source is the
second argument. So my destination in this case, where I want to store the data is R zero. Again,
I put a capital R, you don't have to you can use a lowercase r. It's not actually case sensitive,
I just prefer the convention for capital letters. So that's what you'll see me you. So I'm going to
move into our zero, the value, so I put a comma, and I'm going to specify the source. So my source
could be a number of different things. And we're going to go through these different things. As we
continue learning about assembly because we can, we can move data from registers, we can move
data from memory, so we can do all sorts of things like that. In this case, I'm going to
be moving a constant value into this register. So the way that we do this is we put in a hashtag,
and then we put in the value that we want to move and I want to move in the number 30 in decimal. So
just put hashtag 30. All this does is it takes the number 30 it places it into register zero. And
that's it. That's how we move the data into the register. Now, one thing I want to point out to
you is that you can put hex into these registers as well. The way that you do that as you put 0x
and then the hex value that you want to put in so like for instance 0x Zero A, we write the value
zero a into register zero. So I just want to point out that that's the way that You do hex. In this
case, I'm working in decimal. And I'm pretty much always going to be working in decimal unless
I have a very specific reason to be using hex, just because I'm most comfortable with decimal. So
that's what you'll see me using throughout this. So I moved 30 into register zero. And now we're
going to do our portion that ends the program. So remember, I said that I wanted to move into
registers seven, the value one, because one indicates that we're going to exit our program.
And then I want to do a an interrupt. The way I do this is as follows. I say SW, ei zero. SW i is a
software interrupts what it does is it interrupts the program, and it lets the operating system take
over. Like I said before, the operating system then reads the value in our seven, that value
is one, it takes the one that checks the lookup table, it says, Okay, well, one says that we
should terminate the program. And so we're done. So that's really all there is to
that. So straightforward, simple. Let's go ahead and compile and run this.
So I'm going to click on compile and load. And as you can see, it compiles our program
together. And we are now in our disassembly tab, and we can see each of the instructions as it
interprets them. Now, I will note to you that this emulator doesn't really do well with
us wi it doesn't really understand software interrupts all too well, I'm writing this as if
you were reading it for an actual ARM processor, if you're running this on, say, like a Raspberry
Pi that runs on arm as Wi Fi is going to be what you're going to use, but our emulator won't end
up, like executing it properly, such as note, just a little quirk of the emulator, unfortunately, but
um, if you're running on an actual ARM processor, this is what you would end up using. So that's
why I'm teaching that instead of something more specific to this emulator. So let's walk
through these operations. So remember, the first thing is moving 30 into R zero. If I
press step into, it will execute that instruction. Now I want to point out a few important things.
The first important thing is you can see that r zero now has the value one e inside of it, which
I will tell you is the same as 30 in decimal, you want to verify that you can
actually come down to the settings here, under format, you can go to decimal unsigned
or decimal signed, decimal signed assumes that there's negative numbers. Decimal unsigned
assumes that everything is positive. So we can go decimal unsigned, since everything is positive,
you can see that that gives us a value of 30. So you can see that's how we convert between like
the hex and the and the actual decimal value. So that's, that's something that we're able to do.
Now one additional thing that I want to mention is you can see that we started one e inside of here,
if you're familiar with converting hex to binary, you'll know how to read the binary for this. And
you can see that in general, the most significant bit is on the left most side, we actually refer
to this as a specific type of storage known as Little Endian. So Little Endian refers to the
most significant bit being on the left hand side. That's the way that arm functions as well as many
other processors. But an interesting thing about arm is that actually can be used the opposite way
as well. So the most significant bit on the right hand side, which is known as Big Endian, we would
say that arm is actually a by Indian processor, because we can change where the most significant
bit is depending on what we're doing. So that's just something that I've wanted to note about,
like how things are actually being stored in those registers. Then the last thing that I want to
point out here is the idea of the program counter. So the program counter is telling us where we're
currently located in terms of instructions, you can see, right now it's equal to four. And
at address four, we have our move for one and two are seven. So I just wanted to, you know, pull
some attention towards how the program counter was using. That way, you can get a bit more context
compared to the last video where I just told you what it was doing. Now you can actually see it in
action. So you can see in our next instruction, we stepped into our seven gets a value of one, and
then we would end up doing a system interrupt, and that would end our program. And that's really all
there is to it. This is a really basic assembly program that we've now written. So you've now
gotten the basic fundamentals of writing a program and what the actual architecture is generally
looking like. In the next few videos that I that I put up, I'm going to discuss a little bit
about some of the different addressing modes that we have available to us the different ways that
we can move data to and from registers as well as storing in the stack memory. So we'll we'll
discuss a lot more about memory storage. And then from there, we can start to take a look at
some some more advanced operations and then some actual like algorithms and building
some interesting stuff with our processor. So in this video, we're going to take a look
at the different addressing types that exist inside of arm as well as other assembly languages.
And the general idea of addressing types is that there are ways that we're able to store
and retrieve data from the various memory locations that we have. So For example, in the
previous video, we used a type of addressing that is known as immediate addressing. And
that's when we want to move into a register a specific value that is constant. So whenever
we have like a constant value like this five on this side being moved into a register, or zero,
we call this immediate addressing, because we're taking an immediate value, and we're placing
it into a register. A similar type of register type movement that we have is moving between two
different registers. So if I want to move now, what's an R zero into R one, this would be called
registered direct addressing. So we're directly moving a value from one register into another.
So those are two types of addressing that we have that work with the register memory, and they're
the most common ones that we'll typically see when we're working with registers. Now, the more
interesting type of addressing that we have has to deal with data that's stored in the stock.
So to demonstrate this, I'm going to go ahead and show you how to get data onto this stock
first, and then we'll take a look at how we can work with the data that's on the step. So
first off, how do we get data onto the stock? To do this, we have to use a data section in
our application. And the way that we do that is we just type in data, this is going to come
below all of our like global start portion here. So you say put it just right down here. And what
we're going to do is we're going to declare any data that we want to put in our stack memory.
And we do this in the way of giving it a label, which basically functions like a variable name,
that we then declare the type of the variable, and then the data that's actually stored inside
of it. So for example, I'm going to declare some data, I'm going to name the data list, I'm going
to put list colon, and then I'm going to go to the next line. And I usually like to indent to put
the next portion here just organize it nicely. And in this case, I want to store a list of
numbers. So in this case, when we're dealing with numbers, numbers are going to be a specific size.
In this case, I want to work with 32 bit numbers. And as we've discussed 32 bits is considered
to be a word. So I'm going to type in dot word, this tells it that every single one of the values
that follows should be treated as a word type, which means that they are 32 bits in size. Notice
that we don't say like it's an integer, or it's a float or anything like that. It's simply it's very
basic, it's basically just got those those basic sorts of data types, usually you're gonna see
things like ASCII, or you're gonna see things like the actual size, like Word or halfword, or bytes
or something like that. So in this case, now, we can just start listing off the numbers we
want to put inside of our lists. So I'll just put some some random numbers in, it doesn't really
matter what numbers you use, I'm putting in some some positives and some negatives, so you can
see the different types that exist. Now, the very first thing that I need to do is I need to be able
to retrieve where this list is located in memory. And what we do is we typically look for the first
entry in the list, and then everything is going to follow sequentially from there. And basically,
what's going to happen is that they're going to appear in every single like slot in memory
sequential from the first one. So you'll see that when we actually load up this program.
So in order to get the first memory location into a register, we want to enter registers that
we can actually work with it and manipulate it, it's easier to work in the registers than it is
to work in the stock. So we want to get it into the register first. And to do this, we're going to
use an instruction known as El de R. And what ltr lets us do is it lets us load data from stuck
into registers. That's the main purpose of it. So I'm going to load into register R zero,
the location of the first value in our list variable. So this equals list indicates that
I'm dealing with this list in my data section. And what it's going to do is it's going to find
out where this first value is located. And it's going to place it into registers zero. This is
known as direct addressing. And this is how we essentially initialize the location of our list.
So let's Compile and load and see what happens. I'm going to go ahead and step into this
LDR instruction, and we're going to see that it's going to store the value eat into
the register R zero. So what does that mean? It means that if I come into memory, and I go
over to memory location eight, so this is zero, this is four, this is eight right here, you can
see that this looks like the start of my list. So if we look we have four five, negative 910 to
negative three. So I've got four, five, this might look a little bit weird. It helps to switch over
our view here in our settings to decimal signed to see it a bit more clear. You can see that
that's negative nine. Just as a Moodle reminder, the reason why we see it like this is because
it's stored in two's complement, right? So we have all the F's, which is all the ones and then the
actual number there, right? So you can see that that's generally how that stored but this helps
us see it a little bit more clear. So you can see that these are all the entries in C I'd have our
list. So that's, that's nice and easy to look at. And now we actually have the location of the first
value in our list. So that's nice that we have that location. The next question is, well, how do
we actually retrieve the value from that location? And the answer is that we have to use another
type of addressing mode. And this one is known as register in direct addressing. And the
way that that works is that we still use LDR, because we're moving from the stack into the
register. And what I'm going to do in this case is I'm going to load into register
r1, the value that exists at the address of Rs zero. So these square brackets tell our
assembler that we're really looking to find the value associated with the address in
R zero. So when we compile and load this, what's going to happen is, in this case, we
get 10 as our value in R zero, or one, zero. So you can see that it changes every time that
I compile and run it, that's totally normal, every single time that you run it, the
memory structure will be slightly different, which is why we sort of have to do more dynamic
loading, like we're doing rather than hard coding numbers. And so it's just something to note as
well. So you can see that our zero is equal to 10, we come into memory, we can see that indeed,
10 does look to be the location of the first element. When we do our next instruction, this
LDR R one and then the square brackets are zero, what it does is it takes a look and it
says okay, well when r is zero, we have this value here. And then what it will say
is they'll say, Okay, let's go to that memory address what's stored in that memory address,
okay, four is stored in that memory address. So we'll return that back as our results. And you
can see, that's exactly what we get is four. So just to give you a little bit of an
analogy, if you're thinking about like other programming languages, it always helps
to think about this sort of like a high level world as well. In a lot of programming languages,
you might have like a list, you know, I could do like a Python, like list, for instance.
And we can have like our different values, like this. And what we're doing with this, with
this register in direct addressing is basically just trying to find the lists value at i, where i
is Rs zero. So it's really like the list value at zero. That's basically what we're looking to do
with this. So that's just something to know. Now, there are other ways that we can access values
off of this list. One such way would be to use register indirect with an offset. And what
an offset does is it starts with the value in our zero. So for example, in this case,
it's currently 10, it adds some offset to it, and then retrieves the value. So for instance,
what I could do is, I could add four to it, because if I add four to it, that would take
me to the next location of memory. Remember, this is 10, this is some 14 here. So I can add
four to it to get to the next location in memory and retrieve that value. So that would be an
example of something that we could do. So let's see how we actually do that, we would do LDR. In
this case, I'll put it into r two in this case, and we're gonna do R zero, has to take four. So
what this does is it takes the value in R zero, it adds the four to it, and then it retrieves that
value. If you sort of like doing R zero plus one, in the high level context, I do plus one here,
because we're just moving one index over in the list. Remember, we're doing four here, because we
need to add four to this address here in order to end up at this location, which is the location
of the next value in memory. Such as generally how that ends up working, right, it's four in
hex for each stock location. And of course, we could do other things, right, like we could add
eat to get to this, we can add 12 to get to this, and we can keep going like that as well. So that's
just something to keep in mind with this too. And just to demonstrate to you that this really
does work, let's Compile and load it and let's step into it, we can see we get 10. In this case,
four is the location of the first memory. And then five is what started the second location.
And we can confirm that that actually is true, we can see five follows from four. So we can
see that that does work the way that I have explained it, which is great. So the last
two types of addressing methods that we'll talk about here are known as a pre increment
and post increment. So I said previously, the the first one that we have here is the same
as doing like lists that are zero plus one. With a pre increment, what we would be doing is
we would be incrementing, our zero, and then we would be accessing the value of our zero, so we
essentially increment it, and then we check it. So that's the way that a pre increment works. So
it increments before it actually gets the value. And the way that this works is we just put an
exclamation mark after this. So it's the same sort of syntax, we just put an exclamation mark
that indicates this is a pre increment now. So let's see what ends up happening. When I compile
and run this you can see that I get 10 Still forest still in this, and you can see what's going
to happen as the result is going to be the same, we're still going to get five, because it's going
to increment this by four, which would take us to location 14. In location 14, we have the value
five. However, something very interesting happens that's different from the previous example and
nuts that are zero changes to 14 afterwards. In the first example, where we're doing
just an offset, our zero didn't change. When we do a pre increment, which has
the exclamation mark at the end, it increments our zero, and axis is at that specific
location. So with this differs from our offset. And now finally, we'll talk about our
post increment, our post increment is very similar to our pre increment just different
than when we actually increment the value, right? It'd be the same as doing an access
to our list our zero, and then incrementing, our zero value one index further. And the way that
this is typically written is just like this would be our zero, and then hashtag for like this. So
that's generally the way that this will end up looking. So let's Compile and look that and see
what happens. So you can see again, we're at 10, as our starting location, four ends up being our
value for our one. And then for our two, we end up with the value four, and then this increments
to 14. The reason why we got the value four is because remember, our zero started at the
beginning, which is 10. And then we ended up accessing at that location giving us four and
then we increment it afterwards. So remember, the increment comes afterwards, rather than before. So
that's the main thing to keep in mind with that. And that covers all of the different address
loading types that we have available to us. So now you should be able to comfortably
manipulate data and be able to retrieve from registers and retrieve from Stack memory. And
we'll use this throughout various other videos as we continue on learning arm. So you'll get
very familiar with these different addressing types as we work with them, and you'll see
like good applications of them as well. In this video, we're going to discuss some
of the basic arithmetic operations that exist in arm assembly. In addition to discussing
these, we're also going to discuss a few different modifications to these instructions that
tend to be present in a lot of the instructions in arm assembly. And they lost to do very
interesting things like setting the CPSR register after the instruction is run. And the reason
why we're going to discuss these first with arithmetic instructions is because arithmetic
is easy. everyone sort of knows about adding and subtracting and multiplying. So
it's a very easy sort of place for us to work with these instructions. Once we get
a basic understanding of these instructions, we'll take on some of the more interesting
and complex constructions. Allowing us to be able to discuss these ideas with a solid
understanding and foundation already in place. So to start off with the very basics, we can
add, subtract and multiply numbers in arm using a set of instructions. So the first is add,
the second is subtract, and the third is multiply. Now you'll notice that division isn't
included in this division turns out to be a fair bit more complicated than the other three
instructions, so requires a little bit more finesse to get it working. So we aren't going
to discuss any division we'll discuss addition, subtraction, and multiplication. So when we
want to add two numbers together, for instance, it's fairly straightforward to do, what I'm
going to do is I'm going to move some data into some registers. So I'll add some, we'll put
some numbers into these registers. And then we can use our ADD operation or add operation is going
to take in a destination and two sources. So the destination for this it's going to be R two. And
then I'm going to add together two numbers. So I'm gonna add together RS zero, and R one. And what
this is going to do is it's basically going to do this, it's going to put into R two, the result of
R zero plus r1. So that's all this is really going to do for us. So just going to add the two numbers
together and store the result in our two. So that's nice and easy. Let's just take a look at
how this works just to get comfortable with it. So a compile and load. You see I can load five
into register 07 into register one, I add those two together, and I get c which is equal to 12.
So five plus seven equals 12. So we can see that the addition instruction works successfully. So
that's the way that addition works for us and the other instructions work very similarly I
can change this to sub and what that will do is it will do the results are two equals R zero
minus r one. And then we can also do multiply. And what that will do is it will do R zero times
r one. The main thing to keep in mind here is that R zero is always the first operand
and then r1 is always the second operand, which only really matters in subtraction, right?
And addition and multiplication, the order doesn't really matter. But in subtraction, it does,
right. And you can see that when we take five and subtract it from seven in this example here, we
would clearly get a negative number, right, we'd get negative two as a result. And that's where the
interesting ideas start to come in with arithmetic operations. So if I were to do this,
right, now, we can see that I get five and seven. And when I subtract them, I get this
big number here, this big looking number here. And when I convert this to decimal sign, you
can see I get negative two, right? But if we're looking at it in hexadecimal, it doesn't really
necessarily clear. Because the reason why it's not necessarily clear is because we could have had
a very large number and RSC row and maybe a small number and our one that when I subtract them, I
would get potentially this very large number here. So the question is, is this a very large
number? Or is it a negative number? There's some overlap there, right? Because of the
way that we store negative numbers in assembly, we don't necessarily know right off the bat, if
this is a negative number. I mean, I clearly know now because I put in five and seven. And I know
that subtracting those two gives me a negative number. But think for a minute, if we had, say,
a user input, and we subtracted two numbers that the user gave us, how do we know if they give us
a very large number or a negative number? Right? How do we know the difference between those
and just sort of maybe drive this point home, I'll just show you what I mean by it could have
been a very large number, if I were to put this as as a hex, right? So I can say, let's say
1234567872, all F's like that. And then I would have to subtract one from that, right?
And we'll just change around this orderings, that it's the first one. Well, actually, no,
we can keep it like that, because we want to do our zero minus or one. So what I mean by
it could have been a large number is when I have this result here and I subtract it from this
number here, you see the result is the exact same, right? We get this all F's and the E at the
end. So you can see if I take this big number and subtract one from it, I get this. But also,
if I take five and I subtract it from seven, like we did before we get the same results.
So this is why there's the question of well, is it a negative number, or is it a very large
number, subtract it from a very small number. And the way that we solve this problem is using the
CPSR register, which is this register right here. You'll notice some of these letters
along here, the N, Zed, the c v i, all these sorts of letters. Each of these indicate
the specific flag that is set in this register. The end, for instance, stands for negative, so it
will tell you if the results of the last operation was a negative number. And that's exactly what we
want, right? We want to set that flag so that we can understand that this result is negative. And I
could do other things to it can take care of them if there was a carry in the operation if there was
an overflow, if the result was zero, and that sort of idea there. So how do we get that to actually
set because if you notice, when I compile and load this and I step through these instructions,
nothing happens to the CPSR register? The answer is that we have to
use a special type of instruction called a in this case, I'll call it an arithmetic
with flags. And the way that it's represented is we just add an S to the end of our instruction,
so sub s in this case, so sub s is going to set the flags in the CPSR register. And you
might be thinking, Well, why doesn't it just always do this? Why doesn't the subtraction
operation just always set the CPSR register? And the answer is because to set the CPSR register
requires at least one additional operation, right? It requires us to actually load data into
that register. And that, you know, adds a little bit of overhead to the operation. If we don't
need to do that, then we shouldn't. So that's why there's two separate instructions for this
because one of them is slightly more efficient than the other and slightly efficiencies do matter
when you're programming at at such a low level. So then the second question that you might ask
is, well, when should I use sub s over sub? And the answer is you should use sub s, if one you
know that there's going to be negative numbers, or two, you don't know what the values that are
going to be subtracted are. That means you're loading them from some location that you don't
have control over. If you're just taking two constant numbers and subtracting them from each
other and you know what the result is going to be, then you can use you know, whatever instruction
is appropriate. If it's going to be positive, just use sub that's okay. If you don't know that you
should use sub s. So you can check to see if it's positive or negative. So just to demonstrate what
this ends up doing, when I compile a note this, like I said before, when we do this,
now, you see that this CPSR register has changed. And you can see that n is now bolded that
means and it has identified that the result was a negative number rather than a positive one.
And that's exactly what we were looking for. Now, just to note, the reason why this is such
an eight is because so each of the four bits at the beginning of the CPSR register stands for one
of the flags. And that's the same with every bit, every single bit is one of the flags. So since
it's set to 1000, that means that negative is set to one, which means that it was negative. And then
I think that the other four are the other three zeros, or the zero, the carry and the overflow,
which are all set to zero. And that's why they're grayed out here. So that's the way that those
flags are actually set. So if you're not working in an emulator, you'll just see that set as
eight rather than seeing the end highlighted, the end being highlighted is nice for when you're
learning because obviously that tells you hey, it's a negative number. But if you're doing this
just like on like a Linux system or something like that, you won't see that you'll just see the
eight instead. So it's it's good to understand exactly what that's doing as well. So that would
be the idea of our arithmetic operations. Now, there's one other additional arithmetic
operation that is quite helpful for us. And it's the fact that when we add two numbers
together, in some instances, you might end up with a carry. And basically, what that means is that
the number was way too big to actually be stored inside of a single register. So to give you an
example, if you have like, you know, something like this, and you want to add it to maybe
something like this, right, so we'll just say, we'll add this. And this is another example
where you would want to use something that actually sets the flags. And the reason
being is because when you do this addition, you'll notice that when you add these numbers
together, it's far too big to actually be stored in one register, because it overflows beyond 32
bits, right. So see, when you do this, it actually sets the carry flag in here to indicate that there
was a carry that happened in this operation. So these are the sorts of operations where you end
up with potentially a carry on, right. So when these sorts of things happen, we want to be able
to catch them as we did here. But we may also want to use that carry at some point later on. So if we
want to add a carry operation, then what we need to do is we need to use the ADC a operation, what
this will do is it will add a query to a result. And what I mean by that is basically so if
we were doing this, this operation here, the result would be r two equals R zero plus R
one plus the carry. So the carry will basically be a one if this flag is set equal to one, or it
will be a zero otherwise. So that's the way this works. So it's either set to zero if the carry
is not set, or it's set to one if the carry flag is set. So that's the way this ADC operation
works. And there are other ones as well. So we can subtract as well. Using the same sort of idea,
we can also multiply with the carry. But the most common would it would be the odd which would be
a a DC like this. So this gives you an idea of some of the fundamental arithmetic operations.
And we'll see these as we discuss other assembly programs. But this just gets you familiarized
with how we add, subtract and multiply numbers and some of the little quirks of setting flags
when we do these different types of operations. In this video, we're going to take a look at basic
logical operators in arm. So these are operators such as AND or exclusive OR, AND negation that are
used to complete bitwise operations on registers. We're gonna take a look at how these operators
work. And in addition to that, I'll show you a few basic things that we can do with these
operators, aside from the typical ideas of, you know, comparing if two inputs are the same
or not, or those sorts of ideas. So starting off with the instructions, the end or exclusive, or
they all work very similarly to the arithmetic operators that we saw in the previous video. And
by that I mean, so for instance, if we were going to do an end, we would have a destination for
the results and the two things that are being ANDed together. So that's the way that our and
operator will typically work. So for example, if I were to do like a simple, let's move on let's
move into our zero with value. FF. Let's move into our one the value 22, for instance, and let's add
those two together and store the result inside of our two. When we compile and load this you'll
see that what we get is we get the two F's here, and this one we get 16 and then the result
of adding those together. is 16, of course, because all of the inputs in the first one are all
ones. So wherever the ones match up, we get one, wherever this year was matched up, we get zero.
And that gives us the exact same result back. So you can see that that works the same way that we
would expect any typical logical end to work. So this would be the idea of our n. Now, we can also
do n and that sets flags using an S. So I just always want to note that with these instructions,
typically, there's always a complimentary version that will set flags as well. Most commonly,
you're going to see just and being used, since we don't usually have flags getting set with
the end operation, it's mostly just a matter of completing the logical operation and storing
the results. So that gives us or an instruction or or instruction is similar. It's O R, R, for
or so just keep in mind, it's not or it's with two R's. So again, just to demonstrate how this
works, you can see there are two inputs are FF and 16. And when we are those together, we get FF
because of course, anywhere that there's a one, we end up with a one. And since f and f is all ones,
we always end up with ones which gives us the FF as a result. So that gives us our or operation.
And then finally, our exclusive OR is e o r. So this is exclusive, or so just to show how
this works, we have the FF the six t, and when we do those together, we get e nine, remember that
an exclusive OR is going to only give us a one, if one of the inputs is one, not both of
them, right? If both of them are the same, we get a zero, otherwise, we get a one. And that's
our exclusive OR so you see that when we do this, we get exactly that result as expected. So those
are the very basic logical operations, they're very simple and straightforward. There's nothing
particularly special or interesting about them. The one that's a little bit different actually
is negation. And that's one that I want to talk about now. So with negation, we do something
slightly different than just doing like a knot on the actual register itself, you might expect
that you might have like a knot operation that does that negation. That's not the case in
an arm, what we have instead is we have a special instruction called Move negative m v n
like this, what move negative does is it moves from the source the negation of the source
into the destination, so it negates the source, and then puts that result into the destination.
So it doesn't move and then negation at the same time. So to demonstrate that, let's just do this
with our zero and itself, what this will do is it will move our zero, it will negate it and put
it back in the register that we started with. When we do this, you see that we start
off with all F's, and when we negate it, we get zeros in the first two bits and F's and
all of the later bits. Now, this might seem maybe a little bit strange. But remember, when
we do a negation, it negates the whole register, not just like the single bits or anything
like that it does it to the whole register. Now, when we have something like this happen, we
might want to make it so that the other registers are set back to zero. And the question
is, how would we do something like that? And well, actually, we can use our and operator to
do something like that, right, what we could do is we could do an end storing the result in RS zero,
taking our zero and ending it with something that we know is going to give us zeros for this first
six bits, or those first six hex values, right, which would be 0x 123456. And then the others will
be FF to keep whatever is inside of them. This will clear all of these bits because ending zero
with anything gives us zero. And since these are always one, if we add anything with them, we'll
get back what we started with right, either a zero or a one depending on what the value actually
is. And now the result of this is we start with the FF, we'd negate it. And then you can see
when we do the and clears all the other bits, giving us this result back. I know this might
become a little bit more clear. If I use like, let's do a for instance, when I do a for an
example, you see that we get five, five here, rather than zeros, you see that when we query it,
we keep the five, five and get rid of everything else. That's something that we can use and to be
able to do is clear specific bits and register. So that's one of the applications of and so
this gives you a very basic overview of the different types of logical operators that exist
in arm. This is, of course, very high level and just like very simple examples, right? I just
want to give you an idea of the instructions and idea of what they might be useful for. And
then from there, we can continue to build on top of those concepts. So this gives you the very
fundamentals of the logical operations and then in future videos, we'll see how they might be
used in more practical sorts of applications. In this video, we're going to start to discuss
logical shifts as well as logical rotations. And these two instructions are really useful in
the sense that they allow us to manipulate numbers at a bit level by way of shifting everything
to the left or to the right. And it has a lot of unique and interesting properties that are
actually really useful to take advantage of. So what I'm going to do in this video is, it's
easier to look at this as a bit level operation rather than with hex or decimal. So I'm gonna
demonstrate to you what it looks like from a binary perspective. And then in the next video,
we'll actually take a look at some examples with instructions in arm. So this is just going to get
you that basic foundation of what is a shift? What is a rotation? What does it look like? What does
it represent? Why do we use it, so it's going to be the main call here. So we'll start off with
shifts. With shifts, there's two different types of shifts that we see there's a logical shift to
the left and a logical shift to the right. And these two differ based on what direction the bits
in the binary number are shifting in. So to give you a basic example, let's just pick a binary
number, let's say 1010. This is 10 in decimal. So when we look at this number, when we're
doing a logical shift to the left, what that means is that I'm going to move everything
one position to the left of where it started. So this zero moves over here with this one
used to be this one moves over here with a zero used to be the zero move to the one.
And remember, since there's eight bits, I didn't write them in here, but there's,
you know, eight bits of space here available, the one is going to move over here to this empty
bit that didn't used to have anything in it. Now the actual result of this is the following, we get
1010. And then there's an extra zeros at the end. Now when we take a look at this binary number,
we actually see a really interesting property come up. And that's the fact that this is actually
equal to 20, when you translate it to decimal, and notice that this is exactly double what we
started, it's like we multiplied the value by two. And it turns out that isn't coincidental. That is
true for every single instance of a logical shift to left, it's the same as multiplying the value by
two. As such a logical shift to the left actually represents a fast way to do multiplication
by two. And that's the main value of doing this logical shift. Now, there are other
reasons why you might want to do it. But this is one that's very obvious and prevalent. So
this is something that we could take advantage of. So this would be the idea of your logical shift
to the left. Now, when we take a look at a logical shift to the right, it does something very
similar, but in the opposite direction, right. So we start off with 1010, which again, is
equal to 10. If I shift everything to the right, it would mean that this one shifts over here with
a zero used to be the zero shifts over to the one, that one shifts over to the zero, and then
we just get rid of the zero at the end, right. So this zero just gets it disappears,
right. So we don't really care about it anymore. Another thing that I'll just note here
quickly, remember, there's technically zeros to the left of this, right. So this zero
here moves into the bishop where this one used to be. The result of this is the following, we
get 0101. And what is this equal to? Well, this is equal to four and one, which makes five, notice
that this is exactly half of what we started with. So it turns out that when we shift to the
right, it's the same as dividing by two. So remember that we didn't really have a
way to divide numbers before there isn't really a division operation in arm. This
is a way that we can implement division, we can do divisions by two, which allows us to
be able to actually do full division operations. So this allows us to have division. And again,
there's other reasons that we might want to do shifts to the right or shifts the left. But these
are the two most obvious ones that are really valuable to know about. And just in case, you're
really wondering, like, why is this true, you can you can intuitively figure out why this ends up
multiplying by two, right? If you think about what shifting to the left really does is it increases
the power of all of the powers of two here by one, which is the same as multiplying by two, whereas
this one decreases all of them by one, which is the same as dividing by two. So there is actually
an intuitive reason behind that if you want to go through and actually sort of like derive why
this is true. So it gives you an intuition behind logical shifts. And hopefully it gives you a bit
of an understanding of why we care about them and what they are useful for. The next thing that
we'll talk about is a rotation, which is our O R. And a rotation only really differs from
a shift in one single way and that is that's when we talked about the shift to the right I said
okay, this zero we just get rid of it right so the right mouse thing we just get rid of it. We don't
care about it anymore. With the rotation that doesn't happen. The right most thing actually loop
back over to the left most position. So let me show you an example of that. Suppose that we have
the following. So do all eight bits this time. And suppose we have something like this. So
first off, if I did a logical shift to the right, what you would end up with is you would end
up with, you know, we have all the zeros here, and then we have zero, remember, everything shifts
over to the right one, so we would get 0010. This would be a logical shift to the rights in this
case, right? Now, if we were to do a rotation, but we end up with instead, as we end up with
this, we end up with 10000010. Notice that this one moved to the front rather than just getting
eliminated, since gives you the difference between the logical shifts in the rotation. Now, you
might ask, why would we want to use a rotation, the uses of a rotation are actually
a lot more abstract than a shift, where we're dealing with rotations, we're
typically using it for things like hashing, we use it for crypto, we use it for graphics,
it's not an entirely common operation to see, it's typically actually left in for historical
reasons. And one thing that you'll notice because of this is that arm doesn't actually have
a rotation to the left like an arrow, l, this does not exist. So we can only rotate to the
right. If you want to do a rotation to the left, you can technically do it with rotation to the
right. So if you want to shift n bits to the right, what you would do is you would do sorry,
if you want to shift n bits to the left, rather, what you would do is you would do a
rotation to the right 32 minus n times. And you know, you could sort of play around
with that and see that it actually is true, what that does is it rotates over to the left
hand side rather than the right hand side. But again, these sorts of operations, the
rotations are not very commonly used, we don't see them all that much. So they're sort of
just something that I'm adding it to here to say, hey, these exist, there are some applications
of them and hashing and crypto and graphics, you might see them at some point. But they
aren't entirely common. But this is how they work just in case you ever do see them. So it
should give you a good groundwork foundation for logical shifts and logical rotations.
In the next video, we're going to actually go through and see how we can implement
these things in our arm assembly emulator. In this video, we're going to take a look at
how logical shifts and rotations are actually used in arm. So we're actually going to take
a look at some practical examples and see how these instructions actually work. And be able to
demonstrate that they do actually do as I said, multiplying by two and dividing by two, and make
sure that these results are actually truthful. So to start off with, let's look at a very
simple example, we'll do something similar to what we did in the previous video where
we were working on the binary numbers. So what we did was we had 10, and we did some
chips on it. So we did some shifts to the right, some shifts to the left. And we'll take a
look at what those results end up being. So to start off with, I'm going to go ahead
and move the value 10 into register zero. Once we have done this, what we're
going to do is we're going to do, let's do a logical shift to the left to start
off with. So the way that we do this is we do LSL we type in the register that we want to shift. And then we're going to indicate
how many times we want to shift. So what I was demonstrating in the video previous
was that we could shift one time to the left and we would be shipped one time to the left it was
the same as multiplying by two. With arm we can actually specify to shift multiple times if we
want to. And the way to think about that is that it's the same as just multiplying by two repeated
amount of times. So if I shift to the left twice, what I'm doing is I'm multiplying by two twice, or
multiplying by four, right? If I shift three times that I would be multiplying by two, three times,
right. So that's the idea of what we're looking at when we're saying how many times we would
like to shift. So if I want to shift everything to the left one time, which would be the same as
multiplying by two, I simply indicate that here. There's something that's interesting about
this is that you can actually use this to shift by a variable amount, right? So I
could put in like a register like r1 and say shift by the number of times that's inside
of the register r1, right. So there's ways of being able to combine this together to be
able to utilize like a variable amount of shifting. So that's one thing that you're
able to do that I just want to note here. So with that being said, let's take
a look at what this actually does. So we can pile on load, the first load is
going to put 10 inside of this register, and we're gonna go to change this to
decimal just that way you can see it a bit easier. So you can see that we have 10
here. The next time that we move through we do our shift which changes it to 20 and as
you can see it does as I said it would it multiplies it by two. And just to continue on
With this, we'll go ahead and do a rotation to the right and just what we'll do is shift to the
right rather, and see what that ends up doing. Now, what I'm anticipating this is going to
do is it's going to shift to the right by one bringing us back to 10. And let's just see if
that actually does happen. So we'll go ahead and compile and load, we started at 10, we ended
up at 20. The next shift brings us back to 10. So you could see that generally, this is working
the way that we expect it to. So this is nice and simple, nothing too complicated here. Now, one
thing that we can do is shifting that's quite interesting and unique is that we can combine it
with the move operator to do a move and a shift at the same time. This is a really useful sort of
thing to be able to do. To give you an example, maybe I don't want to shift R zero, maybe I want
to store the result of multiplying R zero by two into register r1. So just to demonstrate what
that would look like currently, with their current instruction sets, I'll just go ahead and
get rid of this line. What I'm saying I want to do is I want to move, you know, r into r one, the
value r zero. And what I want to do is I want to do a shift on r one by one. So what I'm doing
is I'm preserving the value of R zero, right, so that way, the value of R zero doesn't
actually change R one stores that value instead. Now, rather than having to do the move and shift
in separate instructions, we can actually do them together. So what I can do is I can add
on a third piece here, I can see LSL. One, just like this, what this is going to do is it's
going to move the value stored in RS zero into r1. And then it's going to shift it by one. So
let's take a look at what happens. So we get 10. And you can see we get 20 directly. So you can
see that it does that shift immediately, right. So we don't have to worry about it happening, you
know, multiple times, right, we don't have to do it in multiple separate instructions, we could
do it all in one instruction instead. So this is something sort of unique about this shifts that
we haven't seen with other sorts of instructions so far. So that shows you how the logical shifts
work inside of arm. I'm going to end off here just by showing you a rotation, just that way,
you're able to see what that ends up looking like. So the instruction for rotation is, of course
are or like we said. And of course, we could do some examples of shifting by one for instance,
some, although we should pick a number that will actually flow over into the remaining, you know
end. So that goes over to the left hand side. Let's see, I think I've value perhaps like, well,
that you like 15 should definitely do it right, because we would have all ones on the right hand
side. So let's go ahead and give that a try and see what happens, we get 15. And you see when we
overflow over we get this massive number here, this one that actually helps to look at the
hex to see what ends up happening, right. So you can see that we have eight as our first hex
value, which is 1000. So you can see that the one shifted over to the front, we still have seven
on the right hand side, which implies that we have you know all of the ones remaining except for
the one that was on the end, right we have 0111. And then on the other hand, we have 1000 on the
left hand side. So you can see how this rotation is generally working, it is doing sort of the idea
of what I was saying would happen with it. So this gives you an idea of how the rotations work. And
I want to note as well that you could do the same thing with rotations that we did with with moving
right, we can put it into the move instruction, and we can rotate as loyal as the move right.
So the same sort of idea is what we did with the shifts before system instructs you how
rotations and shifts work in arm. So you now understand what these operations do what their
value is, and how the instructions can be used. In our next video, we'll take a look at some other
instructions through arm and we can get a better understanding of some more advanced things
that we can do with this programming language. Now that we understand some of the basic
instructions in arm, we can start to take a look at some of the typical high level constructs
that we would see and find out how they map generally into the assembly language. And the way
that we're going to start with this is by taking a look at statements related to conditionals.
These will be statements sort of like IP statements in higher level languages. We want to
take instructions based on the result of something else that has happened in the program already,
perhaps something related to a user input or something of the sort. And we want to be able to
take different paths based on the values that are provided. So in this video, I'm going to give
you some really basic examples of how this can be done. So the main way that we do these branching
instructions is using comparators and branches. So it can be error is something that allows us
to compare two values to determine whether or not they are greater than, less than or equal
to each other. Branches, on the other hand, allow us to move around our program to different
locations based on the result of comparisons. So for example, we might want to move to a different
part of the program if a result is greater than another, or maybe if two results are equal, we
move to a different location in the program. This is very similar to the way that if statements
and L statements work in your typical programming languages. So let's take a look at a really simple
example. I'm gonna go ahead and move some values into some registers, we'll do some comparisons and
see generally how these branches actually work. So let's move to our zero the value one. I'm going to move to into r1. And now I'm going to
do a comparison between these two values. And the way that we do this is we use a cmp. That's our
comparison instruction. And then we give it two arguments. And we'll discuss how this generally
works once we've got it written out here. So what a comparison is really doing is it's
going to take a look at R zero and R one, and it's going to do the following computation. Gonna
do R zero minus r one, so it's going to subtract the two from each other. Why does it do this,
because this is a way of comparing two values, we know that if R zero is bigger than r1, then
the result of this will be some positive number. If our zero is smaller than r1, the result
will be some negative number. And if they're the same as each other, of course, the result is
going to end up being zero. So we can know those three results based on the subtraction of the
numbers. So what will happen is the computation for the compare will do this subtraction. And what
will happen is this CPSR register, this one right here is going to get set based on the results of
the subtraction. Now as we briefly discussed in this CPSR register, we can set different things
like negative zero carry overflow and these sorts of flags. Well, it turns out that that's very
useful for this situation, because suppose if we subtract these two numbers, and I want to see if
our zero is smaller than our one, if our zero is smaller than r1, I expect to get some negative
number. And that would mean that the negative flag should get set in the CPSR register. So
you see how all this sort of fits together, we've sort of put the puzzle together. And we
can see how we can use all of these different pieces to get our conditional statements. So
this is a very high level construct that we're able to replicate in assembly. For that matter,
when you compile your high level languages, it's using this sort of idea in the
background, maybe not exactly like this, but similar sorts of ideas to this. So understanding
that what happens after we do this comparison, well, in this case, maybe we want to check to
see if so let's say we want to check to see if R zero is bigger than r one. So we want to see
a greater than r1, what we can do is we can use a branch greater that which is written as b g t,
so it's B as a branch and then GT is greater than. And what we do is we give this a location,
and the location is going to be some label that we did also discuss briefly the idea of
labels. So start is an example of a label. And we can actually add more labels if we would like. So
I can add in a label like greater, for instance, this would be the label that will run when the branch greater than occurs. So what I can say,
I could say b g t, then I can see greater. What will happen is when it does this comparison,
if it finds that R zero is bigger than r one, it will move to this greater label, and it will start
to execute whatever is provided here. So in this case, we'll just do something to see that this
is working, we'll move the value into our two. So that would be an example of this. Now, let's
go ahead and run this and just see what ends up happening. So you can see generally, that these
values actually are greater, right, our zero is not greater than our one, which means that we
shouldn't actually take this branch here. Now the question is what happens after if we don't take
this branch? And the answer is we just continue on as if we never actually executed this instruction.
Let's maybe make this a little bit more clear. Let me add in a move instruction inside of here.
Right, so we can add in this move instruction. And we could see that when we do this, the comparison
ends up not being a value that's greater. You can see the negative flag is set indicating that the
subtraction was a negative number. When we do this, you see that it skips over this branch
and it just moves into the next instruction. So you can see how that generally works. Now,
if it were greater, right, if I said three, for instance here, and we ran this, what would
end up happening is we do our comparison, you see that we don't get a negative
number, instead, we actually get a carry. And this indicates the value was
bigger rather than being smaller, you'll see that we can move into this
branch for greater than you can see that rather than moving into this instruction,
it skips it and goes to this greater label. So this gives you an idea of what's actually
happening with these branches. Now, generally, when we're doing these sorts of instructions, it's
important to note the flow of assembly languages. If the value isn't greater than when we have
this instruction, here, we saw that it moved to this instruction. However, once this instruction
finishes, assembly will continue moving through until it reaches the next instruction. And you
can see what ends up happening is we end up in this greater branch anyways. And that is,
of course, a problem we don't like that. But we want to do is we want to skip the
greater branch, we want to make sure that we don't actually reach it if we're not greater than
that value. To do this, we can use branches as well. So there's a special type of branch called
a branch always be a L sub branch and then always, and this will always execute. So we'll always
branch this location, regardless of what happens. So for B A L, what I could do is I could say
rather than going to greater, I can go to a different one, you know, maybe I'll call this
one default. So in this case, we can create a default label. And then we could do something
here. So I can do the same thing as before. Right, so we move to into our two. And we can
see that in this case, if we don't reach this greater branch, we move here. And we always branch
to default, which skips over grader and ends up here instead. This allows us to effectively bypass
this greater instruction. Now, another thing that we need to keep in mind here is once we finish
the greater portion, right, we will also fall into the default case. So it's just very important
to note that the instructions are always going to run sequentially, I want you to understand that
very clearly that everything is going to run sequentially here, right. And I can show you
this right. So if I compile and load this, we have three we have to we subtract them,
we do the branch for greater than we end up in this move instruction, right. So we do this
move instruction. And you can see that we end up in default, next, right, and then it runs
out instruction. So it's very important to note that it's always going to move sequentially,
right. So if we don't want to go into default, after greater, I should add in another branch to
move somewhere else. So this is just something to keep in mind when you're writing these
branches. Everything runs sequentially, that can be a little bit tricky. So just be very
careful of that stepping through the programs, it's going to make it very clear what is going
on, you'll understand it completely once you step through a few programs. But just keep that
in mind and keep thinking about that as you're writing your code and running it and practicing
with it. So this gives you a general idea of the different branches and how they generally work
in our program. Now there's of course a lot of different branches. And I'm not going to like show
off, every single one of them are going to have the amount of detail for them, I'll just sort of
like lay out a few of them here that are common. So if greater than and greater than or equal
to, we have less than and less than or equal to. And then we have equals or does not equals those
the different branches that we have available to us. And they're fairly intuitive, and they may
write each u equals any not equals lt is less than L e is less than or equal to so on like this, you
can get a whole table of these through the arm documentation, there's actually a lot
more than the ones that I'm showing here, I'm going to show you the most basic ones
that will come up most frequently. If you ever find yourself needing a very special branch,
it likely exists. And you can find that branch and the actual documentation. But this gives you the
main overall picture of what branches are commonly used. How do we use branches, and what is the
effect of using the branches. So that's everything for this video. In the next video, we're going to
see some applications of branching in the form of looping. So branches allow us to implement
looping, good repetition as well. So we're going to take a look at this sort of concept and
be able to build some interesting applications and hopefully help you understand this idea of
conditionals and branching a little bit more. In this video, we're going to talk a little
bit about looping as a consequence of the conditional statements that we learned in the
previous video. When we discussed the idea of conditional statements, one of the big benefits
that we can get out of them is the ability to repeat instructions. Of course if we could branch
to a specific label, we can always move back to a specific starting point and repeat instruct shins.
And we can always exit that repetition using a comparison and some form of branching as you
would with like a for loop or a while loop, right, so you're able to get that exact sort of idea.
Looping, while some sort of condition is true, or while some sort of condition is not true,
as well as looping a specific number of time. So we can get both of those loops working in
assembly in the same sort of way that we would in a high level language, maybe with a little
bit more finesse required to make it actually work. So to better understand this, we're going to
take a look at a relatively simple example, that's going to draw from a lot of the things that we've
learned so far. So it's going to be a great way to sort of refresh your knowledge and apply some
of these instructions in a more practical way. What I'm going to do is I'm going to
start off by defining a list of numbers. And this number list is just like all
the numbers from one to 10. So something very simple for us. And what we're
going to do is we're going to load this list into memory, and we're going to try
to iterate the list. So the first few parts out, it's fairly simple, right, we're going
to load the list into memory using LDR. So that part is it's no big deal. And then what
I'm going to do is I'm going to start by loading the first element into my into my memory, I'm
going to use R one to store the elements of the list. And what I'm going to do is I'm going to
add up all of the numbers inside of the list, and I'm going to store that result in our two
sets sort of the main idea of our program. So let's go ahead and do that. We'll start by loading
the value of the first value of the list into r1. Of course, we use that using addressing
based on the address that was placed inside of RT zero. And then we can do the
addition to add that first value into our to do sort of like initialize everything right. So that gives us sort of like an initialization.
And then we can actually start to talk about the looping portion of this. So this starts up
the list. So what we've done so far is we've sort of set to the first value of our sum equal to the
first value in the list. And then what we're going to do is we're going to iterate past that point
and add those values on to that or to register to we've reached the end of the list. It is an
interesting concentrate, how do we find the end of the list? Well, let's take a look at what the
list looks like in memory, good code and compile and just step into it to find the location on
my list. It's that location 10 in memory. And you can see it right here, you see all the
numbers from one to 10. And then afterwards, we get all of these A's repeated. Now it turns
out in the case of my memory, I have all A's as a representation of an empty slot in memory. So
it goes to serve that if we have this set of A's, we've reached the end of the list. So I'm going
to continue to move through the list iterating until I reach something that is equal to this.
Now how we do that check is actually a little bit complicated, we actually have to do
something sort of interesting here. Now, when we work with assembly, you might think
okay, well, that's easy, I can just compare, you know that value, and then we'll be done. But
the way that assembly, specifically arm assembly handles literals is a little bit weird. So when
we have those immediate values, or literals, they can only be a specific size. And above that
size is typically two hex values. So if we have something bigger than two hex values, we have
to be a little bit more creative with how we deal with this. And the main way that we're going
to deal with this is we're going to use constants. And constants isn't something that we've really
talked about so far, but they're very simple. At the very top of our program, we're going to define
our constant using a keyword that is.eq You. So EQ you like this. Similar to defining our
list, we're gonna give this a name and a value. And I'm just gonna go ahead and copy it because
I don't want to mess it up here. So just copy this over there. And there you have it, that
is our constant defined. So this defines a constant called the end list, and it gives it
a value, which is this value here. And then we can load this into memory using an LDR. Like
we've done with the with the list loading it, it's going to load this into R three,
just to have it available to us. So there we have this value now
loaded into R three. And now we can actually do our loop. So I'm
gonna start by creating a label for our loop. And what our loop is going to do
is it's going to load the next value in the list So what this will do is we'll increment by
four, and then it will load the value of that address into r1. And then it's going to take
a look at this value and see if it is equal to that location in memory that is empty. So this
all these A's here. So remember, we stored that in our three, so we're going to compare R one, two,
R three. If those are equal to each other, we want to leave the loop. So I'm going to say B Q exit,
I'm going to put a label exit here. And the label exit isn't going to do anything, it's just going
to be the exit point of this loop. Alternatively, if we have not reached the end of the list, so
what I'm going to do is I'm going to add the value into our to, and then I'm going to loop again,
so I'm going to branch to the top of the loop. So to understand this, what's going to happen is
it's going to load the next value in the list. If that value is equal to the end of the list, so
this set of memory that sort of is initialized, then we're going to go to the exit, otherwise,
we're going to add that value to the total. And then we're going to loop again. Now this isn't the
only way that we could find the end of the list, we could know the length of the list, that could
be another way that we might be able to do this. And there are likely other tricks that work in
assembly to be able to do this. However, mostly when we have the length of the list and makes
things a little bit easier for us to be able to do. There's an interesting idea of uninitialized
memory, it can be a little bit unpredictable, right? We don't necessarily note these values
are going to be stored this way. So the way that I'm doing it right now is a simple example
that's going to demonstrate to you the idea of looping. In the real world, when we're doing this,
it's a little bit more complicated, what we would end up doing is we would end up putting a special
value at the end of this list, you know, something predictable, and then when it loads into memory,
we know what value is at the end of the list, I can actually give you a practical example of
this. If you're familiar with C programming, you'll know that characters and strings
have a special value at the end of it. And it's this backslash zero character, it's
called the null Terminator. What this is doing is it's indicating the end of the lists
that when we iterate, we know where to stop. So that's just to give you a bit more of a
practical idea of how we do this. But for now, we'll assume that we're in this perfect
world, we're uninitialized memory stays put, and we don't have to worry about that. But
just in case, you're curious about that, that would be the way that we would usually handle
this sort of idea. So with that out of the way, let's just talk about what this program does,
let's go ahead and execute it. So you can see that we get the location into memory, we then
load in that end location, so we can check for it, we then start by loading the first value, which
is the value one, and we add that on to the total. Now we get into the looping. So you can see the
next value is to recheck if that's the end of the list it's not. So we add that value to the
total. And then we go back to the top. And then we continue doing that. And you'll see that this
continues on for a while, right. So we're gonna do this for every single value inside of the list,
it will just keep looping through right, right 789 and 10. And once you reach 10, you'll see that
the next value is going to be that end of the list all of the A's there. So at this point, we compare
them and we see that they are equal to each other. And you see that it skips us out to the exit
rather than adding it to the total and to looping again. So at this point, we've exited the loop,
everything is done, we've iterated the list, and you can see that the value
that we got at the end was 55, which is the value of adding one to 10 inclusive.
And that's the way that we set up our loops. So using this sort of concept, you could do all
sorts of different loops, you could do for loops, you could do while loops. And you know, you're
able to sort of expand your knowledge from here. This gives you the basic fundamentals, it shows
you how you can use the branching for the loops. And from here, you can apply this in many
different ways whenever looping is actually required. So that's everything for this video.
In the next few videos, we'll take a look at some other instructions that we're able to use related
to this idea of sort of structuring our program through conditionals and looping and see different
ways that we can manage this sort of idea. So in this video, we're just going to discuss some
of the fundamentals of conditional instructions. Now conditional instructions allow us to be able
to complete a specific instruction based on a condition. So similar to the comparisons that
we saw with branches. What we're able to do is we're able to do a comparison, and then specify
to do another operation based on the result of that comparison without the need to branch around
the program or do anything complicated like that. So it allows us to create a more condensed
form of our branching exercises that we were looking at in the previous few videos. So let's
discuss a little bit about this with an example. So suppose that I were to move some values into
registers. We'll just move some random values doesn't really matter what they are. So I'll move
to into R zero and maybe we'll move forward to r1. Now we'll do is we'll compare R zero and r1. And of course, we know that the result of this
comparison is that our zero is less than r1, right? Because if we subtract two from four,
we get a negative number. Now in this case, maybe I want to add one to register two, if
the result of this was actually less than, if we wanted to do this in the previous videos,
with the knowledge that we had, what we would do is we would do something like this, we would
say, Okay, we would branch less than we could call it like, add our two, and then we would have like
an ad or two that would do exactly that, it would add to the register, r to the value one, right.
So that would be the idea of what we could do in order to achieve that effect. And then we would
have to have a branch over here to skip over it just in case, you know, we didn't actually want to
do that, right, just in case, it wasn't less than that we could skip over it and say, like, maybe
skip over to like an exit. And that would be, you know, over here. So this would be the way that
we would have done this before with just branches, right. And this is not bad. But there's a lot
of lines of code for this specific instruction. And really, it doesn't require as much like of
this code as what we really want, right. So we can condense this a little bit more using these
conditional instructions. So the way that these conditional instructions work is that with things
like add, you can add on a condition. So I can say add LT, what that means is add less than, so what
will happen is this instruction will only trigger if the comparison that came previous is less than
so if R zero is less than r one in this case, then we will do this addition instruction.
So like I was saying before, we'll try adding one to R two. And let's just see what ends up
happening. Right. So you can compile and load, we see that we get two and four into those
registers. When we do our comparison, you see that the negative flag gets set in the CPSR registered,
that tells us that the value was less than right, so two is less than four. And now we come up
to this ADD instruction. And it should only add one to our two, if the value of the previous
comparison was less than. And we can see that it does add one because the value was less
than. So you could see that generally this is how this instruction tends to work right. And
there are other variations of this instruction, it's not just add that we can do this with
we could do this with a lot of the different instructions in arm. So there's things like
um, like subtraction and multiplication, we could do with the move instruction as well. So
just to give you like another example, we could do something like this like move greater than
or equal to the value five and to our to write, this would only trigger if the value is greater
than or equal. So you can see when we run this, and we go through our instructions,
it doesn't actually do anything, because the value is not greater than or equal.
So the move instruction doesn't execute. So that'd be an example of you know, whether it will
execute or not. And of course, we can flip this around to be able to get it to execute,
there's two different ways to do that, we can move a bigger number to our zero. Or we can flip
this comparison, right. If I flip this comparison, it now does R one minus r zero, so four minus
two that's a greater than or equal to value. And when we run this, now, you'll see that we
get to four, you know, we do the comparison, you can see the results in the CPSR register will
result in it moving five in because the result was greater than or equal to. So just wanted to give
you have an idea of the sorts of instructions just to show that they exists in assembly, just to be
able to make your code a little bit more simple. Because having to run branches all the time is
very complex. We don't want to have to do that every single time. So this allows us to reduce
the complexity of our applications. Overall, it just write simpler code. So that's everything
for this video. In the next video, we're going to dive into the idea of functions, which are a
bit more of a complicated way of doing branching. But as you probably know, core to most
higher level languages is the idea of a function. So we'll dive into more detail to get an
understanding of how functions actually work out a low level perspective, and get some familiarity
with writing functions with arm assembly. So in this video, we're going to start to
discuss the idea of functions in assembly. And specifically, we're just going to take a look
at how we can call and return to a location using these functions. So we're not going to dive into
a huge amount of detail. I want to give you sort of the higher level functional flow of things.
And the purpose of doing this is that well, functions tend to be very complicated. A lot
of People approach this by showing you the full picture immediately. And we start getting into
all these complicated register interactions. And it's very hard to understand. So I want to
break it down into smaller pieces that it's a lot easier for you to be able to grasp every
single individual concept. And then we can put it all together and really run with it and see
how things are working with a full example. So to start off with, we already know sort
of the base fundamental ideas of calling different locations and our program, right moving
around to different labels in our application. So let me give you a really simple example.
Suppose that we wanted to create a program that added two numbers together. And we wanted
that to be a function, because maybe we might do it multiple times throughout our program.
Well, in order to do this, we're going to go ahead and start by moving some values into
some registers that we want to be added. And related to to make these values, whatever you
want, I'll just move into our zero or one. And then what I'm going to do is I'm going to branch
off to a function that's going to add the numbers together, and we're going to call that add two. So
we'll call it a su. And then I'll define it here. And all this is going to do is it's just going to
add the numbers together and store the result in our two. And there we have a very simple idea
of a function. So when we run this, of course, it should work the way that we expect it to
right, we get one to three, we branch over to our add function, it adds the numbers together,
we end up with four and everyone is happy. Now, my question to you is, what if I
wanted to do something slightly different? What if I wanted to continue execution directly
after this function call? So what I mean by that is that maybe after this function call, I want
to do something different, right? I want to, you know, move some more numbers into registers, you
know, maybe I want to like move into R three, the value four, right. So if I want to do this, I have
to have some way of coming back to this location after this function is called. Now there are a
few ways that you could achieve this sort of idea, right, like I showed in a previous video, what
we could do is we could branch after this ad and we could branch to this location here. And then
after that location, we can branch beyond the the add two functions, so we don't actually
run back into it. This creates a little bit of complexity. And we don't really like that
complexity, it's a little bit weird to follow. So in these sorts of situations, where we're just
calling this as a function, what we can do is we can add in what is called a return address. And
with a return address, what we're able to do is we're able to return to the function where we
started. So we're able to at the end of this function returned back to the location directly
after we called that function. So this is the idea of a return. So that's what we're going to take a
look at here to take a look at how we can do this. The first thing that we're going to do is we're
going to change this branch to a B L, which means a branch links. And what that's going to do
is it's going to store the location that follows this branch inside of one of our registers,
which is this one here, the LR register or link register. So without doing anything further,
let me just show you what that looks like. So when we go to this point, here,
B L, you'll see that LR gets 00000 and then C, right? What that location corresponds
to is the address of the instruction that follows the branch. So you can see here at sea, there's
this move instruction, which is right after our branch. So you can see that what this
link register stores is it stores the location directly after the call or branch to the function.
So in order to make this work, after we finish this here, we should go back to the location
that stored inside of the link register. So in order to do this, we can use a another
instruction known as A B x, this will branch back to the location stored in registers. So in this
case, we can branch back to LR which will branch us back to the link register. So let's pull that
all together. And let's see what that looks like. When we do this, we move the values into our
registers, we branch into the add function, you can see that it's storing see as our return
location, which is this move instruction right after this branch. You'll see that we add the
values together and our two and then when we branch it takes us back to this move instruction.
So you can see that that actually worked. It brought us back to the location where we started.
This mimics the sort of flow of a typical function right if you are familiar with higher level
languages, when you have a function you have a return the return takes you back to where you
started. This is the exact same idea. And this is what your higher level languages are doing in
the background when it translates into assembly, this would be the idea of what is
happening with that translation. So this gives you a bit of an intuition behind
how we actually do these branches with returns. Now I'm I'm excluding a little bit of detail
here, there's more things that need to happen to make this work completely functionally, right.
You know, if we have more complex programs, there's certain safeguards that we need to
take in order to make this week as as good as possible. However, for the time being, you have
a good understanding of branching with the links, how that link register works, and how to return
back afterwards. So that's what we're going to talk about in this video. This gives you the
fundamental ideas of functions and subroutines with returns. And now in the next video, I'm going
to discuss a little bit more detail of how we can say preserve things before we move into
these functions and then be able to utilize that to our advantage. So that's what
we'll sort of go into next after this video. So one notable idea about functions and
sub routines is that they often have to do calculations inside of them, in order to
get the result that we're looking to get. In order to do these various calculations, we may
need to use different registers in our processor. Now the number of registers that we have an
arm are, of course, limited, we don't have an infinite number of registers to work with. So
because of this, we may need to reuse a register inside of a function that's already being used
outside of the function. In these sorts of situations, we need to be able to preserve what we
had before we called the function and then be able to restore it once we're done with the function.
You can think of this concept similar to local variables in the function, when we declare a local
variable in the function, its scope is to live inside of the function. Once we read the function,
we no longer need that variable. So the variable is just de allocated and a garbage collector
will come and you know, remove the memory and all of that fun stuff. So what we're doing here is
we're emulating that sort of idea, we're going to allocate some registers in our main sort of aspect
of our program, we're going to call our function, we're going to use the same registers, then we're
going to restore the values back to the main, this is done in the background in high
low level applications, typically, the compiler is responsible for setting up the sort of
register allocations. So this is something that we would see in sort of the low level assembly
that comes out of compiling of programs. So it's something that is done by your high level
languages without you really knowing about it. So let's take a look at the details of this. So
suppose that we have a function where, you know, some stuff was happening beforehand, and then
we call a function, and then we continue on as normal afterwards. So maybe some of the stuff
that was happening beforehand is using the registers R zero and R one, maybe they have
some values inside of them. So let's say, you know, R zero has the value of one.
Let's say r1 has the value of three. Now what's going to happen is we're going to do
a branch for a function, I'm just gonna call a function get value. And then we'll go ahead and
create that function. So we'll say get value. And what this function is going to do is,
it's going to need to use our zero at r1, we'll just pretend all the registers are full
right now. And R zero and r1 are the only ones that we can really work with. So let's suppose
that we're going to use those two. So we're going to say I'm not going to move some values into
R zero, we're going to do some values into r1. And then we're going to do some sort of
calculation. So I'll just add the two together, perhaps and store the result in R two. And then
we'll return back to the link register. And we're done. So in this situation, you can see quite
clearly that when we move into this function, R zero and R one are overridden, right. So
if I were to compile and load this right now, you know, we would have one in three,
then we branch we get five and seven, we do our calculation. But when we come back
to the beginning of our function, we no longer have these results available to us, right, we
no longer have the R zero and R one values. So to be able to help resolve that what we're
going to do is something interesting here, what we're going to do is we're going to push
the values of r zero and R one onto the stack memory. So we're gonna do this or is a push R zero
R one. What this is going to do is going to place the values of r zero and R one onto our stack,
right? So it's gonna place them onto the stack. And then what's going to happen is once we're
done doing everything related to this function, we're gonna go ahead and pop them off the
stack like this, or does it pop R zero, and R one. And then what I'm gonna do is I'm
just gonna I'm just gonna branch to like an end label so we can just sort of like get somewhere
that isn't going to have, you know, the function interfering with it because otherwise we just move
right into the function again. And that would be the end of this. So what's happening here is when
we move the values into R zero and R one, we then push them on to the stock of memory. So we'll
get pushed over here onto stock memory somewhere. And then what's going to happen after that is
we're going to call this function, we overwrite R zero and R one, we do our calculation,
when we return back to the link register, we come back here. And then what we do is we pop
the values off of the stack back into our zero at r1. So it's going to pop those values and place
them back where they started. This is preserving the values of r zero and R one on the stack, and
then placing them back when they're needed again. So you can see that the result of this is that the
values for r zero and R one don't get overwritten, they get used locally in the function,
but we don't overwrite them permanently. So let's run this and see what happens. So you
can see that we get one in three, two, R zero and R one, and then we push them onto this doc, if
I come over to the stack memory, here, you can see we have three and one. So when we're thinking
about how popping works, just to just to sort of clarify why we're doing this pup, the way that
we are, what's gonna happen is we're gonna pop the first thing into the first register. And then
the second thing into the second register, right, since the stack pointer currently points
at this, you know, FF FF, eight address, what happens is, it's going to take the value
off of that address, which in this case is one, it's going to pop it on to the first register,
which is our zero, which is this one here. And then we're going to pop three, onto r1,
which is this one here, right. So you can see that it just pops one after the other like that,
starting at the top and moving down to the bottom. So hopefully helps you understand a little bit
about the ordering of these two operations. Now when we do our branch, you'll see that
we come into here, we get five in our zero, we get seven and r1. We do some calculations with
them. And then we branch back to the beginning, where the branch back to the beginning, you see
that we hit this pop instruction. Now watch what happens, right, the stack pointer currently points
at the location of one and three, right here. And here. When we go ahead and run this operation,
what you'll see is that our zero is now one and our one is three, you can see that it actually did
preserve those values, right. So it actually did place them back into their respective locations,
this shows that we can actually preserve these values, so they can be used in the function
and then sent back to their original values after the function, right. And then of course,
we have our branch that just takes us to the end. So hopefully, this helps you understand the
idea of how we can preserve values in our functions. And we can use these sorts of
ideas in a lot of different ways, right? One other way that we can use this is
actually with return values, right? Suppose that our two was also being used, what
we could do is we could do something like this, we could say, okay, so push our two onto the
stack. And then we come back here, we pop them, we'd currently be pointing at our two. So what we
can do is we can place it in some unused register, right? So if I want to get that return address
value, I could say, Okay, let's place it into an unused register. And maybe, you know, our nine
is unused for instance, right. So that would be able to place us at our nine, instead of having to
be you know, somewhere else, right? So instead of having to keep it in our two, we can place it on
the stack. And then we can retrieve it from the stack afterwards, this would be a more traditional
way of returning values, although I think often values are just returned on the into register
itself. So we don't really have to worry about this sort of idea. Because the thing is, with
values being returned, if you return a value, odds are you're going to be using it at some point
in the code, right at some point soon in the code usually. So what happens is we place it into an
unused register, and then we just use it right. So that's the typical case with these, with these
returns, you know, otherwise, you'd have to pop it off and place it in into its position immediately.
So you need it in an empty register regardless. So that's why we don't usually do that, I just wanted
to show you that just in case you ever see it, it is something that you can't do, I think it's not
usually the case that you would end up doing that. So with this, you now have a full picture
of subroutines and functions in arm, you understand more of how to preserve values,
how to set values onto the stack and remove them from the stack. And this allows you to be able
to write fully functional sub routines for your programs. Set sort of sums up a lot of the logical
ideas of what we would typically talk about first sort of an introduction to our assembly type
course, for the next few videos that I put out, I'm going to be showing you the ideas of working
with hardware with the armet processor, which is actually going to be pretty straightforward
because we actually have some hardware devices here on the right hand side. So I'm gonna show
you how to interact with those hardware devices get you familiar with hardware, and then
we can start to take a look at some other concepts maybe at a little bit of a higher
level and see how we can interact with our many other system related processes. So that's
sort of where we'll take the next few videos. In this video, we're going to take a look at
how we can interact with a variety of different hardware devices using our ARM processor. On
the right here we have a variety of different devices is going to note right now you need
to be using the arm D one S O C emulator to have these devices for using just the arm
one general emulator, you won't have these devices available to you. So you have to use
the D one SOC emulator. So with this emulator, we have a number of different hardware devices
that are available for us to be able to interact with. And I'm going to show you how to interact
with a few of these different pieces of hardware. And once you have an idea of some of the general
devices that we're able to interact with, you'll have a better understanding of how to interact
with the other devices that are available to you. So primarily, I'm just going to show you how to
work with the LEDs and the switches. And these are really just a rudimentary inputs and outputs, the
switches are an input, and the LEDs are an output. So let me show you how the input
works first, which is the switches, the very first thing that we need to do is we
actually need to declare a variable that's going to store the location of our switch, and later our
LED as well. The way that we do this if we say.eq. And then we put the name of the variables.
So in this case, I'm gonna call mine switch. And then we put the location of the variable. So
in this case, the location is right here, this FF 200040, right here, so we're going to put that in,
so we'll put it in hex, right, so 0x, FF 200040. Now there's a good reason why we do this, the
reasoning is because you can't directly load something this large into arm directly. So
you can't use an LDR and just load in this value as a constant value, there's a limit the
size of the constant values that are allowed to be loaded in directly. So if your value is larger
than that, you actually have to declare it as a constant value like I've done here. And then
what you can do is you can load it in like this, you could say LDR. And then I can go into
our zero, for instance, the value of switch. And what you'll see is that when we
do this, we can compile and load. And once we do that, we should be able to see the
actual value Oh, sorry, I forgot a comma here. So don't forget the comma here. So it's
the EQ switch, comma, and then the address. Now everything will compile. And when we do
that, you can see we just get that exact value loaded into R zero. So all we've really done
is we've declared it here as a constant, and then we've loaded it into our R zero register
to be used later on. So if I want to be able to use this register, what I have to do is I have
to be able to retrieve the value that is located at its memory address. So what's going to
happen is each of these switches is going to correspond with a different number.
And a different number is going to be represented in binary based on the switches
that are on. So this right most switch here is going to represent like the two to
the power of zero portion of the binary, this one is two to the power of one, this one is
two to the power of two, and so on like that. So that's the way that this is working. If it's on
it's like a one in binary. And if it's off, it's like a zero in binary. So for example, if I check
this off, it would be the same as one, zero and binary, which is two, of course. So let's take a
look at how that works. What we do is we say LDR, I get alluded to r1, the value that's stored
in R zero. So remember, this is the address of the switches R zero is and we're loading
from the location, right, R zero into r1. So let's Compile and load and see what that does,
you'll see that our zero is currently equal to this memory location. When I step into the next
line of code, you see that we got the value two. And that's because I have the one checked off.
So remember, it's one zero, which means that it's equal to two. And we get to this with all
sorts of different numbers, right? So I could check off like all three of these, right, we would
get 111. Let's just reload it and give that a try. You see that? That gives us seven, right? Because
it's four plus two plus one, which is seven, of course. So using this, we can actually get inputs
from users, right? So I can actually do things based on the input from these switches, right, so
I just put in whatever value I want in the switch, when it loads that value, then we can manipulate
it as we need. So this is a very simple way to gain inputs from a hardware device. Right? So you
may have been wondering throughout this course, like how do we actually get inputs? This
is one way that we're able to do it. And then similarly, how do we output
values, we can do that using our LEDs, and the LEDs are going to work in a very similar
way. When we take a look at the right hand side led the rightmost led represents the first
digit of binary and then the second digit, the third digit, the fourth digit, so on and so forth
like that, right? So it's the same sort of idea. So let me show you what happens
with this. So let's say.eq you because I have to declare the location of the LED
right so it's gonna be led ie comma 0x, FF 200000, like that. So once we have that located, what
we're going to do is we're going to go ahead and load that value into memory like we did before.
But instead of loading data from that location, we're actually going to store
data in that location, right, because we're using it as an output rather
than an input. So we're going to put data into that memory location instead of taking data
from that memory location. So to demonstrate this, let's go ahead and load the data that
we got from the switches into the LEDs. So in order to do that, we couldn't we're going
to do is this, we're going to LDR. And I'm just gonna go to overwrite our zero because I've
already gotten the data from the switches, so I don't need it anymore. So we'll say from
our zero, we're going to load in the LED address. And then what I'm going to do is I'm going to
go ahead and store the data that's currently in R one into the location of R zero. And the way
I do that, as I say, S T R, R one into R zero. So what this is going to do is, so Well, actually, we
could just run the program and see what it's going to do, right, so we can step in, you see that
the first line loads location of the switches, and then the second line gets the value from the
switches into register r1. Right. So currently, this value is B, because that's what's represented
with this. It's 1011. Right, so that's the value that's in here, that'd be eight 910 11,
right, which corresponds to be in hex. Now, once we've done this, what we do next is
we load in the location of the LEDs into memory, which is FF 200000. So you could see that those
two match up with each other, or zero and the LED value here. And then what we do is we store
the value that's currently in our one into the location of our zero. So when I step into this,
you see what happens is the LEDs light up, right, they light up, and based on the values that we
placed inside of register are zero or one rather. So you can see what happened is these are
matching up with each other. Now the switch matches up with the LEDs. The reason being is
because we took the value from the switches and stored it into the LEDs. So you can see that
that actually lights those values up. So we can actually represent a binary using these LEDs. So
this gives you a very simple explanation towards how we can actually work with inputs and outputs
using various different hardware devices. There's a lot of different hardware devices that exist
here, right, there's push buttons, there's seven segment displays, there's J tags, there's there's
a lot of different ones. And really the best way to learn these different hardware devices is just
a play around with them, right. There's lots of documentation online that can tell you information
about how these hardware devices typically work. And I encourage you to try each of these devices
and just see what they do understand them and play around with them, they'll be the best way
for you to really learn those hardware devices. I showed you the very basic ways that you
can interact with them. And from here, you should be able to interact with all of these
hardware devices and see what each of them do. Hello, and welcome back. I hope
that you're all doing well. In this video, we're going to start to
take a look at the idea of programming arm through an actual operating system, in
this case, a Linux operating system. Now the Linux operating system that I'm going to
be using for these videos is called raspian. It's a Raspberry Pi based operating system. And the
reason why I'm going to be using this operating system is because the Raspberry Pi computer runs
an ARM processor. And this actual operating system is developed completely compatible with arm
which makes it a really good choice for us to actually program various different arm things
on it. Now of course, there's lots of different operating systems that use ARM however, this one
is one that is lightweight, it's easy to emulate, and it will support everything that we need. So
it's a pretty decent choice for our purposes. Now, the reason why we're choosing to emulate
these operating systems is because I'm aware that most people probably don't have an ARM device
lying around. You know, Raspberry Pi isn't too expensive. But of course, it's a little bit much
to ask someone to go out and buy a device to learn arm assembly. So rather than doing that, I'm going
to show you how to emulate it so that you can just write it on your computer. And then at some
point down the line if you want a physical device, you can go out and get one of those knowing of the
arm stuff that you will know from these videos. So for the purposes of this video, I'm
using VirtualBox I have an Ubuntu machine. It's running Ubuntu 20.04 I believe and it is a
64 bit operating system, you should be able to follow along on any Linux system. The instructions
are actually fairly straightforward. So let's go ahead and jump right into it. What you're going
to want to do is you're gonna want to download two files onto your computer and I'll put links to
them into the description. The first file is from this downloads that raspberry pi.org work. And the
file is this 20 1704 10 Raspbian Jessie dot zip. This is the operating system image that we are
going to use in order to work with emulating this operating system. There are other versions of
it, there's more recent versions, this one is the one that seems to work most stably with QEMU. So
it's the one that I am going to be utilizing. Now, in addition to this, we're also going to need
a kernel that is working for this operating system. And we can get that from this GitHub link.
Again, I'll put this into the description as well. This GitHub link is going to give you this
QEMU four point 4.34 hyphen Jessie file, if you go to the root of this, you can actually
see all the different kernels that we have. So we have support for a variety different types of
kernels. This one is the one that corresponds with our operating system, though. So we're
gonna go ahead and download this one. Once you have these files downloaded, I've placed them
inside of this QEMU underscore VMs folder, you can put it anywhere on your computer doesn't really
matter. And you're going to want to unzip this 20 1704 10 Raspbian Jessie dot zip file, to turn
it into that img file that I have in a directory as well. Once you have this available, I want you
to install QEMU. And you can do that using sudo apt get install Q and you system. So I've already
installed this, it will take a number of minutes on your computer, depending on how fast it is. And
then once it's completed, you will be good to go. Once you have that all set, we're gonna go ahead
and just run our emulator and the way that we do that as we say QEMU hyphen system hyphen, arm,
and then hyphen kernel. And I'm going to direct it towards the kernel that we downloaded, which is in
QEMU underscore VM slash kernel, this file here, we then want to give it the CPU that we want to
use for the emulation, I'm going to use ARM 1176. Again, this is the one that seems to
work most reliably with this particular distribution. So it's the one that I
will show hyphen M is the amount of RAM or virtual RAM that we want to allocate.
So I'll say 256. That's a good size for this pipe and capital M is the emulated machine
type we are going to use versatile PP. This one is just like one of the various types
that's included with QEMU. Again, one that works very well for this particular example, we're going
to provide a serial as stdio just told everything to work through our standard input output. And
then I'm going to use the Append command to append the following. We're gonna say root dev
SDA to root Fs type equals e x t for rewrites. This just helps us set up the general file system
that we're emulating. So this gets everything set up the way that we need it. And I'm going to put
hyphen, HDA. And I'm going to point it towards the ISO that we have on our computer set is in QEMU
underscore VMs slash 2017. This one here to ing. And then finally, what I'm going to do is I'm
going to forward a port from this computer to our SSH port, which is 22 on our machine. The reason
I'm going to do this is because when you work through QEMU, it's a little bit hard to get things
to fullscreen properly. So rather than doing that, what I'm going to do is I'm going to SSH into
the emulator through my virtual machine here. So to do that, we can just do the following. We do
hyphen, Nic, they're gonna say user come of host, forward equals TCP, and then two colons and 5022
and 22. So 22 is the port that we're forwarding. So that's the SSH port. 5022 is the port that
we're forwarding to. So when we want to connect to ssh on this computer, we're going to use
the port 5022. So that'd be the idea of this. And then we will launch this we're going
to say no reboots, just because I don't want the system to actually reboot I just
want it to boot as is. Once we do this, it looks like maybe I just spelt this wrong. The
machine type let me just double check it should be vs. vs. Tile. Yes, it's spelt wrong.
So you versatile PB like this. Now once we run this, you should see that your
QEMU starts to boot. So this is booting into the Raspberry Pi operating system. If you've
gotten this far, Congratulations, you now have a Raspberry Pi machine running on your computer.
I will note that it's a little bit tricky to get this working reliably. I am able to get it working
reliably every time that I've tried it so far on my computer, which is why I'm showing this set
of instructions. If you have any troubles with getting things set up. It should be relatively
easy to troubleshoot the errors that you're getting. But feel free to comment them as well
and I will try to help you as much as I can. I also plan to try to post this image if possible
somewhere so that it's able to be downloaded. So keep an eye out on the description as well, I'm
going to try to put a link to this virtual machine image so that you're able to access it and use it.
Rather than having to do all this setup yourself. That way you get one that is for certain working
through virtual box. And then from there, you can just sort of fiddle around and get it to work on
your particular system. There's a few things like setting up emulation inside of emulation, like
allowing Virtual Box to run emulators like this, there may be some settings that you need to
tweak in virtual box to get that to work. But regardless, again, it should be relatively
easy to get set up. If not just feel free to leave some comments, I'll try to help you with
troubleshooting some of those errors if possible. So once we have things set up here, what
I'm going to do is I'm just going to run the following command, I'm going to open up
the terminal on this emulated device. And we're going to do is I'm going to forward SSH on
it. So I'm going to say sudo service, SSH start. Once that runs successfully, we're going to just
try to SSH into this machine through my computer and see what happens. So it looks like everything
is good to go. I'll create a new tab here. Zooming in a little bit. I'm going to ssh pi one to 7.0
point 0.1. And then the port, like I said before is going to be 5002 or 5022. Right there. When
that goes through ask you if you want to continue, you can say yes, the password for this is Raspbian
R A s or sorry, it's I think it's Raspberry Pi. Okay, well let's let's double check and see
what it is. We want to say Raspbian password. It should be raspberry. So Raspberry should
be the default password. Let's just try that. There we go. So now it has brought us into our
device. And we are now inside of our emulated device. So with this, we now have an ARM device
that is running on a Linux system. And now we're going to be able to do a lot of really cool and
interesting things with arm we're going to learn about things like system and software interrupts.
And we're going to really just build up a lot of operating system understanding as well, I really
dig deeper into the sort of things that we can do with arm assembly. So thank you for watching this
video. I hope that you enjoyed it. Again, if you have any sort of trouble throughout this process,
please do leave comments, let me know, I'll try to help out as much as possible. And I'm sure some
of the other viewers will be able to help out too. And then we'll be able to get things up and
running like this. And then we'll be able to learn all these different operating system
techniques. So again, thank you for watching, and I'll see you in the next video. In this video,
we're going to start to take a look at operating system interactions by seeing how we can output
data onto the screen. So we're finally going to do like an actual formal Hello World program
where we output hello world onto the screen. And while we're doing this, we're going to explore
the ideas of system calls and system interrupts done through arm assembly. So let's go ahead and
get started. For the purposes of these videos, I'm going to be working through our Raspberry Pi.
In this case, I'm actually logged on to a physical machine, but you can use the emulator we set up
in the previous video. Or you can use a physical machine of any kind that has an ARM processor. If
you want to follow along on something physical, the choice is really yours, it should work on
pretty much any ARM based device. So we're gonna start off by creating a new file, which I'm going
to call Hello World. I'm going to do that through the Nano text editor. And it's just my text editor
of preference you can use whatever you prefer. The extension for this file is dot s dot s is
the typical file extension for assembly based programs. And what we're going to do here is we're
going to set up our program in a very similar way to what we have done in the emulator past. Right,
so let's take a look at how we set that up. We say dot global underscore start, and then we have
our start label. And then for this, we're going to need some data. And the data is going to be
what we actually want to print onto the screen, I'm going to do that declared as a message. Now
we want this message to be text. There's a lot of different ways that we can declare text in
arm and I want to discuss each of them. Now, the most basic type of text type is ASCII. ASCII
is just general characters. And there's something about the ASCII format that doesn't really work
well for a lot of text applications. And that is the fact that it is not null terminated. And
what that means is basically in some lower level programming languages, if you've programmed in C,
you'll be familiar with this idea. When we create a string sits it's really just like an array of
characters. We have something that indicates the end of the array of characters because of course,
as we're going into memory in memory is not always predictable what an empty slot of memory will
contain Okay, so what we do is we pick a special character that indicates the end of a string,
and we call that the null Terminator. So it's a special character set aside, that always indicates
the end of a string, the ASCII format does not have that. So this sort of format works best for
things like individual characters, or situations where we don't need the null Terminator.
In almost every single case, though, you're going to want to have a null Terminator for your
string. So instead, we're going to use this here, ASCII set, I'll call it. And what this does
is it declares a string, but it adds on a null Terminator at the end, which allows us to
know where the end of the string actually is. Now, one other note that I want to make here is,
sometimes you might see dot string, dot string is actually an alias for dot ASCII set. So dot
string is the same as this declaration here. So I just want to note that just in case you ever
see dot string, it's the same as this dot ASCII set. So those are the same as each other. It's
a null terminated string. And then from here, we could just type in the name or what what text
we want to write. So I could say, Hello World, I could put backslash, and we could put this
new line character. And so all that does is it indicates a new line, backslash n.
And essentially, we can add that in because it will get interpreted through the
operating system to write a new line character. So that's something that we're able to put in as
well. Now, I will eventually show you what this looks like in memory. But for now, we're just
going to sort of take this for granted and say, Okay, this will work. And we will continue on
with this. Now, one other thing that I want to note is that when you when you make the call to
write data to the screen, in this case, which is what we're going to be doing, we're actually using
components of the Linux operating system, which is really interesting sort of idea. So you'll see
that from this point on, when we start using the system interrupts and system calls, there's
gonna be a lot of parallels between assembly and C. So if you're familiar with C, it will be very
helpful for you. If you're not familiar with C, not to worry, we will eventually discuss C
and its concepts in future videos as well. So once you've declared the text here,
what we could do is we could declared the length of the text. So I could
say le N equals dot minus message. What this is going to do is it's going to declare
a variable called Le n, as gonna be equal to the length of message This is the way that we declare
that the way that this works is it starts at the beginning of the text. So it starts at this each
character here, and it continues moving through it until it reaches that null Terminator that I
mentioned previously. Once it reaches that null Terminator, it knows it's reached the end of
the string, that is the length and it stores the length in this LGN variable for us to use,
send us all the data that we're going to need to create our Hello World program. Now in order for
hello world to work, in order for us to write data to this screen, we're going to need to declare and
have ready three different pieces of information. The first piece of information is where do we
want to write the message or text or data to the second thing that we need is a reference to
tell us what what we want to actually output. And then the third thing we want is the length,
how long is the thing that we want to output, and those are going to be stored in very
specific locations. So we're going to move into our zero the value one, this one indicates that
we want to place that we want to place the output in the standard output for our computer. So this
would be the standard output, which is where all things output by default, in the case of me being
SSH into my actual computer, the standard output would typically be the command line interface. So
I'd be without output. So this is just generally the way that we output things into the standard
output, we can output the standard in by using zero as our argument, we can output your
standard error using two as our inputs. Now in general, we can also add in
different values other than 01. And two, this will actually take in any valid file
descriptor as a an argument. So we can actually write to files as well. For those of you who are
not as familiar with the Linux operating system in the background, what Linux does is it assigns
things called file descriptors to the files on the system. What happens is when you open up a file,
we are given a unique identifier for that file, which is typically represented I believe, as an
integer. And that allows us to know where we're reading and writing from. So whenever we make
calls to read and write to files, we use this file descriptor which is an integer which uniquely
identifies the file. Because this R zero is taking in some integer value, we can actually provide it
with any file descriptor of a file on our system and it will actually write to that file set
something that's interesting about this. Now, like I said before, we also have to give it the
value that we want to print as well as the length. So we could do that using LDR. So I can LDR into
r1 Send the message. So that's going to load the address of message like we've discussed previously
into r1. So I will tell it where a message is currently located in memory. So it's kind of like
a buffer, right, it tells it the buffer location of the message. And then what we could do is
we can LDR into our to the value of length. This point says the length into register two.
So now we know the length of the actual string itself, we know the actual string that we want
to print, and we know where we want to print it, the last thing we need to do is tell the computer
to print the thing to the screen. Now the way that we do this is actually quite interesting.
What I'm going to do is I'm going to move into our seventh the value for Now that might seem
a little bit random. What's happening here is, when we communicate with the operating system, our
seven is a special register, that actually keeps track of what we want the operating system to
do. So when we do an interrupt, which will allow the operating system to take over the processing,
what the operating system is going to do is it's first going to look at register RS seven, and
it's gonna check what number is written there, that's going to tell the operating system what
it needs to do next, when I give it the value for what the operating system CS is, it sees that
I want to write something to the screen, it's then going to take a look at R zero, R one and R two to
answer the questions of where to write the data, what data needs to be written, and what the length
of that data is, it will then write out the data, and then it will return back to the program
to allow it to continue executing. That's the general flow of what happens when we do this sort
of interrupts. If we want to go into a little bit more detail, what's actually happening here is
in Linux, there's a system called called rights. What this is doing is it's actually calling
that write function. And again, if you have familiarity with C programming, and specifically
like system level C programming, you are likely familiar with the right function, you use that
actually to write files on a Linux system, that's the same function that's being called
here, we're just calling it through a system call in a little bit more of an abstract
way. So I just want to bring out that parallel, just in case you're
familiar with C programming, because it's a really valuable one to keep in mind that we
are actually able to communicate with the system calls in this way. So we set up the value that is
needed in order to know which system call to make. Now let's make the system call, which we do
using SW I zero SW I mean software interrupts so what it does is it interrupts and it tells the
operating system Hey, we need you to do something. The operating system helps us handle
hardware things like input an output, so it is able to do the output for us. Again, it
takes a look at our seven it sees the value four, it says that's an output it then outputs
based on r one r zero R one and R two values. Once that is done, it returns back to our
program and lets our program continue to execute. At this point, what's going to happen is we're
going to flow into the data and weird things are going to happen. In order to prevent that we
have to properly tell our program to exit which hasn't been something that we've really had to do
throughout the emulation type videos. To do that, what we do is we say move into our seven
the value one and then we do an SW AI. So what's happening here is again, we're telling
the operating system, what we need it to do, the argument of one is telling the operating
system to terminate our program. So this time when we do our software interrupts, the
operating system will interrupt it will take a look at our seven it sees one and it says oh,
that means I should terminate the program, and it will terminate it and we will be done. So this is
everything that's involved in creating our simple Hello World program. You can see it's it's not
necessarily all that simple. But now that you have a familiarity with a lot of the general concepts
of assembly, hopefully this does actually seem a little bit easy to actually do right. So a lot of
these instructions are already familiar. There's a few new ones a few new concepts, but this
should be relatively straightforward, hopefully. Now, last thing that I need to show you here is
how to actually run your program. We're gonna do two steps, we're gonna first assemble the program.
So we're gonna do as HelloWorld dot s, hyphen O, hello world dot O. This is creating
an intermediate form file known as an object file. The object file can then be combined
with other object files if required and compiled into what is called a binary. The binary is what
is actually run on the system to execute the code. So to create the binary we do LD hello world
dot o hyphen o hello world. So you can see right now we have three files you have Hello
World, which is the binary I just created hello world dot O which is the object file. And
then hello world dot s which is our assembly code. Now we want to execute Hello
World v2 dot slash hello world. And as you can see, hello world is printed to the
screen so it looks like everything is working as expected. So from this video, you should now have
a better understanding Think of how system calls work and how we're actually able to output data
onto the screen. So thank you for watching this video. In the next video, I'm actually going to
walk through gdb to show you how you can get a better understanding of the actual layout of
memory on this Linux system. Because you can't really see a lot of the details just running it,
I want to show you how to debug and understand the code that you're running with a little bit better
than what we have now. So thanks for watching, and I'll see you in the next video. In this
video, we're going to take a look at how we can actually debug our assembly programs through
the Linux command line. And what I want to do is I want to show you that a lot of the things that we
saw in our online emulator can also be replicated through the actual Linux command line using
programs like gdb. And that's primarily what we're going to focus on, we're going to take
a look at the same program that we had before that was this, this Hello World program that was
able to print out Hello World to the screen, I gonna show you how we can step through it and see
all of the different pieces that are required in order to make this work. So first off, we're going
to launch this program into debug mode using GDB, which is a debugger that comes typically default
on most Linux distributions. And it's a really valuable debugger for assembly as well to other
programs like C you'd often see it used with. So to do this, we just do gdb. And then we type in
the name of our program, which is hello world. And you'll see that we'll get up this screen that will
say all this information about gdb. And then it's been launched successfully. So in order to get
this running, what we have to do is we have to add in a breakpoint, what a breakpoint is going to do
is it's going to stop the execution of our program at a point specified by us, allowing us to be
able to see what the current state of the memory is at that point and step through to the next
instruction and continue stepping one by one, the typical thing that we break on is we break
on the Start label. So we break underscore start. So what this will do is when we run our program,
it will stop at that start label and allow us to start executing step by step from there. So once
we have our breakpoint set, we could just type in run, and you'll see that it started the program.
And it's hit the breakpoint at start. But you can't really see any of this information right
now. So what we have to do is we have to change the layout of GDB. So we could say layout, ASM.
And you can see that this gives us a layout that shows all of the instructions in our application.
So you can see the first instruction which is moving one into our zero. And then we have the
loading of our strings and our length. And then we have our system call. And then we have our
end of our program with the system call for that. So this is giving us an idea of how we can
actually look at the instructions that are associated with our program. Now the next
question is, how do we see the registers, there's two different ways that you could
typically see the registers. The first is, you could say, info, register, and then type in
the register that you want info on. So I could type in, for instance, r 00. Sorry, I had to do
it as a lowercase r. So it'd be info register, R zero. And you could see that it has a value of
zero. So there's there's nothing currently in it, it's set to zero currently. So that's
one way that we could see the register. Another way that we could see the registers
we can actually type in layouts are EGS. And you can see that that will show all of
the registers above our instructions. Now what you could do is you could type in control x,
and then Oh. And that will allow you to switch between the instructions and the registers. So you
could see now I can scroll with the arrow keys. And I could see each of the different registers
that exists, you can see like the stack pointer, the link register the program counter the CPSR
registers, you can see all the information that we would typically have when we were running through
the emulator. Again, that's CTRL X and then type O after doing that, and that will let you switch
your interface between the layout ASM and the layout of registers. So you can have both of them
going and switch between them with Ctrl X Oh. Now from here, we can step to the next
instruction using step i. So I type in step I and you could see that it highlights the next
line. So it executed the first line and moves on to the next one. And we can confirm that
because if I look up at my register view, I see that our zero has been set to one, which
is what happens when we do this move instruction, right we move our one into our zero
and we get to the actually did happen. What step by again. And you could see
what's happened now is it's loaded our string into r1. And you could see that it
has the address of the string now stored in r1. Now the next thing I'm going to talk about here is
well how do we actually see the string in memory? So how do we actually see the stuck memory? The
way that we do this is we do the following we do X This slosh. So basically what x is doing is
it's saying examine, so we're gonna examine a part of memory. And now what we have to do is we have
to tell gdb. How many pieces of memory do we want to analyze? And what format do we want to see it
in. So for example, you could say 10x, what that would do is it would show us 10 hexadecimal memory
slots. So what's going to happen is we specify a starting point. So I would say address. So say I
want to see the address of r1. Oh, put dollar sign r1, what's going to happen is, it's going to
start at the address of r1, which is 20098. And it's going to show me 10 hexadecimals, starting
at that point, so it starts at 20098. And then it goes to the next memory slot, and then the next
one until it showed me 10 hexadecimal values. You can see when I press Enter, that's
exactly what happens. Right? So we see the 20098, right, this is the first slot, and
then the second slot, and then the third, fourth, fifth, all the way up to 10. Right. So you can
see that that generally shows us the hexadecimal values. Now looking at these values here, it's not
necessarily clear what each of these represents, right, we can see that each of these is a
single piece of our stack memory. You know, this really parallels the same ideas as our
emulator, right? Nothing's really changed, they're still the same number of like
hexadecimal values per sort of memory, we're still working with 32 bits for every
single piece of memory here, right? So you can see that we have 1032 bit slots, that's
what's really happened here. Now, of course, looking at the hexadecimal numbers is a little
bit confusing. So what you can actually do is you could specify different formats, I give you these
in decimal by changing the x to a D, like that's, you can see that it gives us decimal
values. Again, still not entirely clear. So let's do something different. We can actually
specify character using 10 C. So C is character, D is decimal, just to give you a few more, here
you is unsigned decimal. So that sort of gives you all the same ones that you would have had in the
emulator. write x for hexadecimal D for decimal, you for unsigned decimal, but then we also
have to see for character, which is nice. When I do this one, what you'll see is our
string, right? Hello, world, I don't quite have enough numbers here. Let me put in 15, should be
enough. Yeah, you see, there we go. We have hello, world, backslash n. And then you see that
backslash 000. Here, let me highlight it here. This right here is the null Terminator that I was
talking about. It's the special character that indicates the end of the string, set something
interesting to sort of point out here, that's the null Terminator for our string. So you could
see that this is each of the different values that exists inside of our memory, right, you can
actually see that each of these does represent the characters that we were expecting, right,
we have the Hello World string with the null Terminator, and the backslash n for the new line,
all stored in stock memory. So this allows you to actually see what's being stored in that memory.
So you could see that you have a lot of the same troubleshooting techniques as we had when we are
working in the actual emulator itself. And again, we can continue through this, we can continue
stepping through as much as we'd like, you could see that we have generally the length of
the string being stored and that we do our system calls and we move on as it's right. If you want
to just like complete the execution, you can just type in run again. And what will happen is, we'll
actually run will take us back to the beginning, we can remove the breakpoint, for instance, and
then run it again. But generally, the way they interact with the application through gdb would
be stepping through step by step to sort of debug problems. And then the way that you would run it
is what we saw in the previous video where we did that dot slash, you know, and then running
the program as is. So this should hopefully give you a bit of a better understanding of
how we can actually debugger applications. And you're going to see this a fair bit. Whenever
we're working in assembly, I'm going to be using this debugger to sort of show you the general idea
of what is happening when we're going through each of these different memory locations. So not to
worry, you'll get a lot of practice with this and I recommend just try and get out with a few
of the other programs that we've done so far. Try writing them in this Linux format, and
then try debugging them and just see what happens. You know, it will help you a lot
with understanding the various components of GDB so thank you so much for watching this
video and I will see you in the next one.