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.