- yesterday
Ready to dive into x86-64 assembly? This beginner-friendly video breaks down CPU registers like RAX, RBX, and more in Yasm assembly. Learn how to use general-purpose registers, respect the ABI to avoid debugging nightmares, and optimize code by minimizing RAM hits. We?ll cover callee-saved registers, function arguments, and why you shouldn?t mess with the stack pointer. Perfect for coders looking to master low-level programming. Grab the free book mentioned and subscribe for more assembly tips! #AssemblyProgramming #x86
Introduction to Registers 00:00:00
x86-64 Yasm Assembly 00:00:04
Recommended Book 00:00:09
Move Instruction Example 00:01:04
64-bit Registers Overview 00:01:52
General Purpose Registers 00:02:24
Two's Complement for Integers 00:03:37
Floating Point Registers Introduction 00:03:54
ABI Importance 00:04:55
Function Return Values 00:08:01
C++ to Assembly Translation 00:08:43
Avoiding System RAM 00:11:45
Optimizing with Registers 00:13:04
Callee Saved Registers 00:14:16
Preserving Registers with Push/Pop 00:16:40
Stack and Local Variables 00:22:00
Function Arguments in Registers 00:25:12
Stack Pointer and Base Pointer 00:31:29
Instruction Pointer (RIP) 00:36:30
Temporary Registers (R10, R11) 00:36:49
Accessing 32-bit Register Portions 00:38:29
Floating Point Registers Details 00:42:53
Conclusion and Call to Subscribe 00:45:35
Thanks for watching!
Find us on other social media here:
- https://www.NeuralLantern.com/social
Please help support us!
- Subscribing + Sharing on Social Media
- Leaving a comment or suggestion
- Subscribing to our Blog
- Watching the main "pinned" video of this channel for offers and extras
Introduction to Registers 00:00:00
x86-64 Yasm Assembly 00:00:04
Recommended Book 00:00:09
Move Instruction Example 00:01:04
64-bit Registers Overview 00:01:52
General Purpose Registers 00:02:24
Two's Complement for Integers 00:03:37
Floating Point Registers Introduction 00:03:54
ABI Importance 00:04:55
Function Return Values 00:08:01
C++ to Assembly Translation 00:08:43
Avoiding System RAM 00:11:45
Optimizing with Registers 00:13:04
Callee Saved Registers 00:14:16
Preserving Registers with Push/Pop 00:16:40
Stack and Local Variables 00:22:00
Function Arguments in Registers 00:25:12
Stack Pointer and Base Pointer 00:31:29
Instruction Pointer (RIP) 00:36:30
Temporary Registers (R10, R11) 00:36:49
Accessing 32-bit Register Portions 00:38:29
Floating Point Registers Details 00:42:53
Conclusion and Call to Subscribe 00:45:35
Thanks for watching!
Find us on other social media here:
- https://www.NeuralLantern.com/social
Please help support us!
- Subscribing + Sharing on Social Media
- Leaving a comment or suggestion
- Subscribing to our Blog
- Watching the main "pinned" video of this channel for offers and extras
Category
🤖
TechTranscript
00:00Hello there. Let's talk about registers in assembly, more specifically x8664
00:06Yasm assembly.
00:13Okay, so first off check out this book. It's a wonderful book. The author is a
00:18genius. It's called x8664 assembly language programming with Ubuntu. It's
00:23free. Anybody can get a copy of it. Just go to the website and
00:29you know, grab your copy. It's wonderful. It's released under a copyleft
00:33license. Author is a genius. Whenever I'm trying to remember things about CPU
00:39registers for assembly, I always go to this section. I say Kali saved. I search
00:44for Kali saved. I already had it there. Kali saved and it ends up being section
00:5112.8.2 register usage. So before we do that, let me just give you a little quick
00:58example. So you kind of know what I'm talking about here. Hopefully everybody's
01:01seen this instruction by now. This is a move instruction in Yasm x8664 assembly.
01:07It just moves data from one place to another. It takes two operands. So, you
01:13know, we have like operand one and operand two. Basically operand one is the
01:17destination operand. So we could put destination there if we wanted to. Operand
01:21two is the source. We could put source there if we wanted to. That actually won't, you know,
01:25compile or assemble, but I'm just saying that's what those positions are for. So
01:31here's an example of a register receiving the value 10. We use the move
01:36instruction and we say we would like to move something into the REX register. We
01:42would like to move the value 10 into the REX register. And there you have it.
01:47There's a move instruction. Okay. So REX is a register. Let me just say real fast that
01:52it is a 64 bit register. Um, in our 64 bit CPUs, every single register or sorry, every
01:59single general purpose register and the plot registers, they have, um, 64 bits available
02:05for us to use 64 bits. Like, you know, ones and zeros. It's like a very, very, very big
02:09number. A long time ago we had 32 bit registers. And so these are like, you know, twice as
02:14mathematically robust or whatever you want to call it. And it also allows the registers
02:21to address, uh, you know, the 64 bit, uh, memory addressing space, which helps us go beyond
02:26four gigabytes of RAM in our modern computers helps us go far beyond what we can even achieve
02:31right now. Um, so we have a register called RAX and, uh, there's a list of other registers
02:39on this page here. So basically, you know, we have the REX register and the RBX register
02:44and the RCX register and so forth. Uh, I have a hard time remembering all these, uh, names.
02:50So I go to this book when I forget, but otherwise, you know, you can kind of think of like ABCD
02:56and then just surround them with R and X, uh, you know, R kind of for register and X for, um,
03:05extra big maybe. So we've got the RAX, RBX, RCX, and RDX registers. And then we have a bunch
03:12of other ones, uh, further down. I like to use these down here, R12 through R15. But first,
03:18before we do that, um, uh, let me just emphasize that, uh, these are general purpose registers
03:24to be used for integers, uh, or data. You can put characters in there for strings. You can put
03:30just like, you know, whatever data you want. Um, inside the machine, we use twos compliment
03:36to represent integers. So it's, it's fine. The instructions that you will do for addition
03:41and other mathematical operations will be fine with these registers, but don't put floating
03:46point numbers in these registers because the computer, the system, the machine won't know
03:50how to operate on that data. Um, the thing is integers are stored as twos compliment. I'm
03:56going to talk about that more in another video and floats are taught are stored in something
04:01called IEEE 754 standard, which is just like a crazy different way of storing floating point
04:06numbers that makes more sense for floating point numbers. But because the data is arranged
04:12differently in the machine, um, you can't, you can't really use normal instructions. Like
04:18you can't add, uh, two floats together the same with the same instruction that you would
04:22add to integers together. You would have to, uh, use special floating point registers and
04:29special floating point, uh, uh, instructions. I'll talk about floats at the end of this video,
04:34I hope. Uh, but basically just keep in mind, these are general purpose registers to be used
04:38for integers and data only. Okay. So what am I talking about? Um, what are we, what are we
04:45seeing here on the side here? Why does it say return value? What is going on with Kali saved
04:50and fourth argument and third argument and so forth? So there's a standard that you're
04:54supposed to use when you program, you can get yourself into a lot of, um, hot water or,
05:00or you can dramatically increase your debugging time. Um, if you don't do this, this is, uh,
05:07this follows a standard called the ABI, which I think is short for application binary interface.
05:13But basically I just like to say the ABI respect the ABI. There's a plan that people came up
05:19with that says, this is how you should use these registers. Don't use them in a different
05:23way. Um, and that way, uh, if everybody is writing, you know, modules and they're all
05:29going to interact together, like I write a module, you write a module, we download a module from
05:34somewhere. If they all respect the ABI, then it's, it's pretty much guaranteed that we can,
05:39we can expect certain things like our registers won't become corrupted after a function call or
05:45that we're not going to corrupt someone else's, uh, function or, you know, whatever. Right. So
05:49you can also use this to benefit yourself. Even if you're the only person who is writing your code
05:56and you're not calling on anyone else's function, um, then, you know, like, how are you going to
06:02remember? Like, what was I using this register for? Was, was I supposed to save that one? Was I
06:05supposed to preserve it? How was I going to return data from that function? Was I going to use
06:09this register or the other register? It's hard to remember what you, what you yourself even did
06:14like a month ago. Um, so if you respect this plan, then your functions will work, uh, not only with
06:22other people's functions, but with you, your own functions from a, from a month ago. Um, the other
06:27thing too, is we have system calls, right? So you have like a system call. Let's say we want to move
06:31something into REX. Maybe I want to exit the system or something. Just forget about this. This is not a
06:36system call video. Suppose I want to do a system call, right? The system call itself is also going
06:42to respect the ABI. So if you start calling other, uh, uh, services in the system call instruction,
06:49like if you want to open a file, close a file, read a file, write a file, whatever,
06:53if you don't respect the ABI and the system call is respecting the ABI, then you might have
07:00some of your data corrupted. So it's a really, really good idea to respect the ABI.
07:05In fact, think about it this way. What if, uh, what if you wrote a bunch of functions for
07:11a long, long time? And then like a year later, you couldn't, you couldn't remember what you
07:14were doing, you know, last year. Oh, what was the return register? Uh, and it costs you
07:19a bunch of extra time debugging. Eventually you'll probably decide to write your, write
07:23your own standard. Okay. From now on, I'm always going to use this register for return
07:27values and I'm going to use this register for the first argument. And I'm going to write,
07:30you'd probably come up with a plan, right? Well, that's basically the ABI, the ABI
07:35covers more than just how to use registers, but, um, register usage is covered in the ABI.
07:41So why would you put yourself through a bunch of heartache and then eventually come up with
07:45your own plan when you could have just been following the correct plan in the first place,
07:50right? So everybody should respect the ABI. So, um, let's see. I wanted to talk next about,
07:59um, the return value. Okay. So let me, uh, let me write up a little function here. Let me just say
08:04that we have a C++ function and we'll just say it's like void F. Um, and we'll say it calls on G and
08:16then we'll have another function called G and actually maybe it's not void. Maybe it actually
08:21does return a long and G just returns the number five or something. Okay. Uh, hopefully you've seen
08:29some kind of a higher level language before like C++ so that you can understand what's going on here.
08:34We're just making two functions and one is calling the other. And the second one is just returning a
08:39value to the first one. So in assembly, the equivalent of this would be, let's make a label
08:43with the name of the function and then a little colon, and then just put a return instruction at
08:51the end of it. Wham, you've got a function. It's not going to work very well, but you do have a
08:56function at this point. So F, uh, I'm going to try to copy the C++, uh, function. So it's a really
09:04good idea. Whenever you're writing a function in assembly, try to imagine what the prototype would
09:09be for C++ and just put it as a comment at the top. So I know how the F function is going to behave
09:15in my assembly module because I put a comment up there just kind of reminding me what the prototype
09:19is for C++. It doesn't really do anything. It just sort of runs. So that's okay. And then I'll make
09:25another function down here. I'll do the long G again in a comment, and then I'll do the actual
09:31assembly version. I'll say G colon, and then I'll return. So just by putting that comment up,
09:36I kind of can remind myself now that, uh, the G function is supposed to return some kind
09:43of a value. Maybe I'll do, uh, let's do a comment up here. You know, do, how about let's
09:49say, you know, I don't want to start adding a bunch of local variables right now. Pretend
09:57that, uh, we're going to print the return value of, of the call to G somehow we're going to
10:03use C out or whatever. We're going to send it to a variable, whatever. I'm just going
10:07to try to keep the assembly as simple as possible. But anyway, the point is G returns something.
10:12So, uh, how do you return, you know, a value, uh, in your assembly function? Well, remember
10:19that higher level languages, part of why they're so awesome is they do a lot of extra work for
10:23us under the hood. And they actually provide illusions to us that make programming easier.
10:28For example, there, there are no functions going on inside the actual machine. I mean,
10:33I guess from a certain point of view there are, but basically the machine is just sort
10:36of like moving data and jumping to instructions and jumping back from, from somewhere. And,
10:42you know, it's just, it's just sort of like jumping around and executing instructions.
10:45There's no actual function. It's so it's like, if you want to pretend, uh, that you have
10:50a function in assembly, well, there is a return instruction that will help you jump back to wherever
10:56you came from, uh, most recently, but, um, we have to, we have to implement more is what I'm saying.
11:03So how do we return a value in an assembly function? We just have to load up the return
11:08value register. If you look at this chart again, RAX is designated as return value. That just means
11:14we have to load RAX up with something. So for example, we wanted to return the number five. So I'm
11:19just going to load it up with the number five and then return. Now we've pretty much translated a C
11:25plus plus function into, uh, an assembly function. I mean, that's really what's happening under the
11:30hood. When you compile your C plus plus program, it's, um, it's just, you know, translating all
11:36the C plus plus into assembly. Another thing that I should probably point out is that you should use
11:43registers as often as possible, and you should try your best not to touch memory. Um, of course,
11:51at some point you have to touch memory when you want to save your final result or send it off
11:55somewhere or whatever. But, you know, imagine that you have an assembly function and you're doing
11:58lots and lots of calculations. You're performing an algorithm or something. Your program will be
12:03so much more efficient by like a factor of a hundred or probably more if you don't hit system
12:09RAM. Cause every time you hit system RAM, like a global variable or the stack or something,
12:14then, um, your CPU, you know, typically in the uncashed, uh, scenario, your CPU has to go talk to the
12:21system bus on your motherboard and then, you know, send a message to system RAM and then wait for the
12:25system RAM to figure out what it's doing and then get a response back. So when your program is
12:30executing on the CPU, you can encounter a stall, which is like your program actually stops executing
12:35for like a hundred clock cycles or more. Like it's a long time, at least in the most basic case.
12:42And how do you avoid that? Just use registers only when you're doing lots of calculations. The
12:46registers are built into the CPU. They're part of the CPU's hardware. So they are lightning fast
12:52compared to system RAM or your disc or whatever.
12:57I probably say this a lot, but you know, part of the reason you would even want to code an assembly
13:03is perhaps maybe you have a big program that does a lot of stuff and eventually you profile the program
13:08and you realize, Hey, this one part of the program is really slow because it gets called constantly
13:13and it's not super efficient. That might be a good use case for writing a hybrid program where you have
13:19multiple modules. Some of your modules are in a higher level language and some of your modules
13:24are in assembly. So you take your most important function that slows down your program that gets
13:30called all the time or like it has like a big loop or something and you rewrite it to assembly so that
13:35you can have more control over how often you touch system RAM to try to make the whole thing more
13:40efficient and, um, you know, reduce the number of instructions and just like whatever. And you
13:46can improve the product of the efficiency of your program. So anyway, you want to try to avoid hitting
13:52system RAM. Your registers are built into the CPU. There's 64 bits that gives us 64 bits of address
13:59space that we can reach. You know, back in the day we had 32 bit systems. That means you could only have a
14:04memory stick that was about four gigabytes. So now we can go much further. So, um, yeah, that's what's
14:09going on with our registers on the CPU back to the return value. Okay. So we have a return value here
14:15goes into the RAX register. And then the next thing here is it's saying that the RBX register is
14:22something called Kali saved. So Kali saved is, uh, important. Remember we said that, uh, we have to
14:30respect the ABI, right? Like you have to respect this convention, uh, because if you don't, you're going to
14:34cost yourself debug time and your functions probably won't be interoperable interoperable with other
14:40people's functions or the system call, uh, instruction. Well, so one of the things about
14:47the ABI is some of the registers are designated as the function that is being called has to be the
14:54thing that saves the registers value so that it doesn't corrupt for the caller. Imagine this. If I
15:00had a function called F, I'm going to do like F and G here. Suppose the function F is going to move
15:06something into, uh, let's say R12 or actually let's do RBX, move the value 10 into RBX. And then it'll
15:15call G for some reason. Maybe G doesn't take any arguments. Maybe G is just kind of doing stuff.
15:21Let's just say that in a simple case, G just moves a different value into RBX. So the thing is when the
15:29call comes back, like after we get to this next line, let's say we're going to do something with do
15:35something, do something with RBX. By the time we get to this line, dude, can I get the line numbers
15:43on this? Oh yeah, I forgot. Okay. So by the time we get to line seven, uh, the register RBX is ruined.
15:50These registers are not local variables. They're not like, uh, you know, tied to any scope or function
15:57call. These registers are just basically global variables that are sitting on the CPU for lightning
16:03fast performance. But that means the RBX I use inside of F is the same RBX I use inside of G.
16:10So that means if G messes up the value of RBX, uh, then if F calls G, then F now has a bad value for
16:19RBX. This is a broken program because we did not respect the ABI. The ABI says that RBX is call E saved,
16:26which means if I use RBX in the G, then I have to preserve the value so that by the time I return,
16:34the value is the same as it was when the call first came in. So how do we do that? We'll do that
16:39with a push pop pair. I'll probably make more videos in the future about pushing and popping,
16:43but I'm going to try to keep it simple. For now, we just say, let's push RBX onto the stack,
16:48uh, at the beginning, and then we'll pop it off the stack at the very end. This basically means
16:53we're going to take the value of RBX. We're going to send it onto the stack. So, so yeah,
16:57we are hitting system RAM. It's a little slower at that point. The ABI will save us a little time,
17:02um, for the other registers. I'll try to explain that in a second, but basically we're preserving
17:07the value here with push and then we're popping it back off. So we're restoring it. So that means even
17:13though we ruined the value at line 14, uh, when F does its line seven, it's going to have the
17:19correct value of RBX. It's going to have the value 10, the book. And I like to call this the prologue,
17:26just meaning this is a little section of code where we're going to set things up. We're going to start
17:30getting ready to do more instructions. And then this at the bottom is going to be called the epilogue.
17:36Like this is just like the finale. We're like, we're, we're kind of cleaning up. We're finishing up
17:41right before we return. If you wanted to return a value, uh, from G, then you could put, you know,
17:48the move, uh, uh, inside of RAX somewhere else. Uh, like you could put it like below the epilogue
17:54or just above the epilogue. It's fine. As long as you're sure that RAX is not getting trampled upon.
17:59So it's important to note also that, um, if I do a, let's say a system call right after that,
18:07let's say I call G and then I do a system call with, you know, like, uh, you know,
18:12some other number as the call code. I want to open a file. I want to close a file. I want to
18:16read or write a file, whatever. I want to ask the system to do something for me. The system is also
18:20going to respect the ABI, which means I'm guaranteed that RBX is not modified when the system call comes
18:26back. If, if the system call, you know, if the Cisco instruction didn't respect the ABI, then my RBX could
18:32be ruined. I'd have to do a bunch of stuff to preserve it. So this is very convenient. Um, it also
18:39means that, uh, some of these registers, let me go down a little bit here, uh, like this R10, the
18:46temporary register and also the RDI. If you decided to use that in the body of your program, which you're
18:52allowed to, these are not designated as callies saved, which means system call could actually ruin
18:58the value of those registers. So suppose for the sake of argument, I really decided that I needed to,
19:04um, use, I don't know, let's just say R10 because that's marked as a temporary. I'll put a value in
19:11R10 and then I'll say, you know, uh, do something with R10. I'm sorry, R10. Then that means by the time
19:21I'm done with my call to G, I should assume that R10 is ruined because even though you're, you can see
19:27right here that, that G doesn't actually do anything, uh, to R10, we should assume, uh, that we have to
19:32respect the ABI, which means the other function could have ruined it. This is especially true if you call
19:38someone else's module or system call or whatever. Same thing for the system call. So if I call system call
19:43and I use, uh, R10 at some point before and then after, then I have to assume R10 is destroyed by the
19:50time I get back from the system call. So that's no good. The cure to that when we're talking about a
19:56temporary or something that is not designated as Kali saved is that the caller has to save.
20:02So we definitely have to hit a system Ram just, just to make a call, anything that we think we need
20:09when we're finished. So I'm going to do a push. This is not the only way to do it, but I'm going to do
20:12a push R10 here. And then afterwards I'm going to do pop R10. So that sucks. And then I have to do the
20:18same thing for the system call. I can go push R10 and then pop R10. And of course, if you were clever,
20:24you probably would, you know, put the call to G inside of that, uh, system call push pop pair.
20:31So you could have one less push pop pair, but I'm just saying there's no guarantee R10 is going to
20:36survive. So you always have to preserve it if you ever want to use it again. So this is like another
20:43way that the ABI kind of can help you save time. Notice how here in the, uh, in the G function,
20:50which I probably should have prototyped. Let me just say, it looks like to me it's a void
20:54and it doesn't take any arguments. So I'm just going to do that. Always a great idea to, you know,
21:00prototype your functions in comments. Um, maybe let me do the same thing to F up here. So F looks like
21:10it's, uh, not returning anything and it's not taking anything. Okay. Whoops. Those are C++ comments
21:18that would not compile. All right. So notice how I'm using RBX. So I decided to preserve RBX,
21:25but there's also other registers that are marked as Kali saved like R 12 through 15 and RBP and
21:32whatever, but I'm not preserving those. The reason I don't have to preserve those is because I'm not
21:36using them. So the, uh, the, uh, the ABI can save you a little time here. Like for example,
21:42I'm preserving R 10 because I'm supposed to assume that I, that I, that they could be destroyed by
21:47the time the function comes back. What if instead of using R 10, I used R 12.
21:54So that means I don't have to surround any calls with a push pop pair because I can trust that whoever
22:01is going to modify R 12 will preserve it for me. But then notice down here, G is not actually using
22:07R 12. So G doesn't even do push pop on R 12. So we save, uh, hits to memory. We don't even have
22:14to touch memory to preserve R 12 because the ABI is helping us sort of stay a little bit more
22:19efficient. Um, so this is great, right? F is a broken function at this point though, because I'm
22:25using two registers that are Kali saved. I can't assume that whoever called F is going to know that
22:32I'm modifying RBX and R 12. So I should do a prolog and an epilogue up here. I'm going to say push RBX and
22:39then I'm going to push R 12. And then, uh, down here at the bottom, I'm going to do an epilogue
22:44epilogue, and I'm going to pop RBX and I'm going to pop R 12, but I've done something wrong.
22:55So the thing is, I'm not going to talk about the stack too much in this video, but the, uh,
23:02the stack is a particular type of data structure that will return to you data in a reverse order.
23:10Then you sent into it. This is great for helping us keep track of function calls and return addresses
23:16as I'll probably try to explain in this video, but basically, uh, the data comes out backwards.
23:21So if I do it like this, then actually by the time the, uh, return statement, uh, instruction gets
23:27executed, RBX will have the value that was intended for R 12 and R 12 will have the value that was
23:32intended for RBX. It'll be backwards. So you have to do your push pops in the reverse order to notice
23:38how it's kind of like a shell that goes outwards. It's like R 12 is first on the inside and then RBX is
23:45next on the outside. Um, that's just what you have to do to preserve everything. So, uh, uh, the F
23:51function works now. Um, the G function, I think it works now. We have a prolog and epilog. Okay. So
24:00we talked about the ABI. We talked about calls. We talked about the registers, um, talked about
24:06call E saved. We talked about the fact that we don't have to save the other ones. I think I can talk
24:11about, um, so, uh, one thing that I should make sure to mention is that these are general purpose
24:21registers. They're to be used for integers, integer data, uh, or like characters or just like regular
24:27data. They use certain instructions, uh, that are not to be used with floating point data. So you
24:33actually should not store floating point numbers inside of your general purpose registers. You should
24:38only store, uh, floating points in special registers, which I'll talk about at the end of this video.
24:43So keep that in mind. Um, floating point numbers are stored differently in the system.
24:48They're stored with us, with the, with a scheme called IEEE 754 floating point. Uh, so like,
24:55you know, the, the numbers wouldn't make sense. Uh, integers and floats are stored differently.
24:58So the hardware is, is wired differently, uh, to operate on two different types of data. So anyway,
25:04I just want to say that this is only for integers and general purpose data. So now let's look at the
25:10other types of registers. So notice how RCX and RDX and RSI and RDI are designated as arguments first,
25:17second, third, and fourth. Okay. So let me, uh, let me write like a quick C++ function here.
25:22And this is going to be, uh, let's say we have like a function that returns some value. We'll call it,
25:27uh, we'll call it F and we'll say that F takes in some arguments. Let's say that it takes in three
25:33arguments. Um, again, uh, we can only use integers right now because we're only using integer registers.
25:43We can't use floating point arguments or return values. Just keep that in mind. So I'm just going
25:47to do like three arguments, long a and b and c. Okay. So we can take in three arguments and maybe
25:53we're just going to return, you know, a plus b plus c. Okay. Simple C++ function. Let's do this
26:01in assembly. We'll make a label and, uh, we'll do a little comment that just reminds us what's the
26:07prototype of, sorry, of the function that we're trying to implement. We stick a little return
26:15instruction at the end of it so that, uh, it will jump back to the caller. And then we'll say,
26:21well, when the, uh, caller of this function called us and gave us a and b and c, how do we get a and b
26:28and c? Well, a is the first integer argument. So it's just going to be the RDI register. So we'll
26:35literally just use a move instruction. Let's say we want to, for whatever reason, use the R12 register
26:41and we'll do something with it later. We'll print it. We'll save it. We'll do whatever. Let's just
26:45grab the incoming argument. So we'll just grab RDI. So I'm going to put a little comment up above
26:51here, you know, grab, uh, a and do something with it. And I'll just write down here more instructions
27:03just to, just to denote that we're doing something with the A value. Um, but I'm not going to write
27:08it down here because I don't want this to get huge. So we'll do the same thing with, uh, the B.
27:12Maybe I should remember that, uh, programming is case sensitive. We'll do something with the B.
27:18It's not RDI. It's the second argument. So that's actually RSI. And then we'll do something else.
27:24We could have used R13 or other registers if we wanted to. I'm just showing you how to grab
27:29incoming arguments. So then we'll grab the C and do something with it also. It's not going to be RDI.
27:35It's going to be RDX. And you can do this up to like, I think six arguments. Let me just double check
27:40here. Yeah. So like R9 ends up being the sixth argument. And, um, if you want more than that,
27:46then the caller will have to put stuff on the stack. And in fact, if you, if you kind of like
27:52understand what's happening here, let me just do like a main function, pretend that there are
27:57arguments there. If F, um, let's say we do C out, uh, whatever F returns, you know, this is like a
28:07typical function call, right? So we'll call F F will jump in there. It'll jump into its body. It'll do
28:12some sort of a manipulation with the incoming arguments. It'll return a final value. Then
28:17that final value gets printed out. So what's five plus six plus seven. That's, uh, 11 plus
28:23seven. So what is that like 18? I'm bad at math. So let's just say that's 18 gets printed.
28:27So what happens is, um, we return. Oh, I forgot to return. I'm going to say compute the, uh,
28:43compute the sum of the things. Uh, and I'll just say like blah, cause I don't want to put a bunch of
28:52addition instructions here. Uh, and we'll just like store in, uh, our 13, let's say. So then
28:59when we have finally, uh, computed our result, I'll just move our 13 into the return value.
29:07And what I'm really trying to say here is if you imagine a function that behaves this way in C plus
29:11plus, then what I've written down below is really what's actually happening in assembly.
29:15Your compiler, uh, compiles the code down to assembly language first, and then it assembles it
29:21down to machine code. So really this is what's happening under the hood. It's not like a special
29:25trick. When you call a function in C plus plus, literally the A, B, and C have been loaded up
29:31just before the, the jump instruction to go to that, you know, line nine, uh, they've been loaded
29:37up with the appropriate values. So that's part of like, you know, the magic of what higher level
29:41languages give us. It's just makes things a little bit easier. Okay. So, um, I've written a bad
29:48function. Notice how I'm using R12 and also R13. If I had only used RDI and RSI, uh, and RDX,
29:57those are not designated as call these saved. So I wouldn't have had to preserve those, but
30:03because I chose to use R12, um, and R13, uh, I have to preserve those for the caller, even
30:09if the caller is not going to use them. So I'm going to do, I'm going to do push R12 and
30:13I'm going to do push R13. And then at the bottom, let me do like a prologue here. The
30:19comment prologue is kind of helpful because it kind of helps you remember like, Oh, did
30:23I forget the prologue? Did I forget the epilogue? They got a match. And then I guess the fact
30:29that you can see the words prologue and epilogue also helps remind you that everything is supposed
30:34to be in reverse order in the epilogue. So again, notice how R13 is on the inside and
30:40R12 is on the outside of the push pop pair. So now my program is good. And, uh, we've,
30:48you know, successfully translated a nice function call there. And, um, we've done a bunch of
30:55nonsense in the middle that I'm not writing down at this point. We were, we are respecting
30:59the ABI. So if somebody else uses our function in their module, then they can be pretty confident
31:04that we're not going to ruin their registers and so forth. We talked about the return value. We
31:10talked about that, uh, these general purpose registers are not for floats. We talked about
31:15pushes and pops. We've talked about function arguments, which right now we can only do
31:22integer arguments and return values. We've talked about, uh, when he talked about the stack a little
31:28bit. So let me, let me just point out that, um, suppose you have a function. I just want
31:35to mention this briefly. Suppose you have a function F let's say it's, it's void. So it
31:39doesn't return anything and it has an integer a equals five. Oh, I think the reason that I
31:44wanted to do this is to show you how dangerous it is to mess with the wrong register, like the
31:50stack register. So suppose we have two, um, variables and then we'll do something with a and a,
31:57with a and b. So now suppose we have two threads. We have an execution thread one and an execution
32:06thread two. If you're not familiar with threads, that just basically means your computer is,
32:10is literally executing, uh, your function or sorry, it's executing twice at the exact same
32:16time. Maybe not the exact same time. If you only have a single core on your CPU, but you
32:21could imagine that that's possible, especially if you have many cores, that's an operating systems,
32:26uh, video, but basically pretend that we have an execution thread. So it's like going through
32:32your program, executing one instruction at a time. Then you launch another execution thread
32:35that also executes your, your, uh, your program one instruction at a time. So at some point,
32:42it's possible that, um, thread one and thread two could be executing F at the exact same time,
32:48right? So I'm not going to talk too much about this, uh, because this is not an OS video,
32:53but basically this could create a race condition where thread one and thread two kind of step on
33:00each other's toes. And they both try to modify the value of a, uh, at the wrong time. And then like
33:06they get the wrong value back because the other thread modified it. So this is why local variables
33:11in a function actually live on the stack. A and B are not global variables. They're actually
33:19temporary local variables that are sitting on the stack. And the way the stack works, I'm not going
33:23to talk about it too much in this video is that, um, well, every function kind of has its own area
33:29of the stack. So when the function comes in, uh, on the stack sits the return address so that the
33:37function knows where to jump back to when it returns and any local variable, uh, that the function makes.
33:42So if you think about it, when thread one calls F, we actually end up with a different version
33:51of A and a different version of B. We could, we could imagine them as being, you know, A sub one
33:56and B sub one. But, um, when thread two comes in and, uh, you know, creates those local variables,
34:01we can imagine that we get two different versions of, uh, A and B also. So this is how the computer
34:07prevents, uh, multiple threads from stepping on each other's toes. If you're using local variables,
34:12local variables are just local to the function. This also helps if a function calls itself a bunch
34:16of times, maybe one call or another might want to see a different version of A or B.
34:21And it would be just incredibly difficult to keep track of that if you were using globals.
34:26But when you use locals, well, they're just basically different variables altogether.
34:31So what I'm trying to lead up to is the stack is pretty important. If you corrupt the stack,
34:37then you probably are going to crash the entire program. You might make it so that a function
34:41doesn't know where to return from. Like when you do the return statement,
34:44it might jump to some other part of the code that doesn't even make sense. And then everything
34:48crashes, or, um, you might mess up some data in a local variable. Like if you mess up,
34:54uh, if you mess up the stack, uh, you might be messing up
34:59the local variable that the caller is depending on. And so then the caller continues to execute and it
35:04sees the wrong value and it just crashes, or you might even mess up your own, uh, local variables.
35:11So don't mess with the stack. The reason I'm saying that now is because this very,
35:15this, uh, register right here, the RBP, sorry, that's not it. The, uh, RSP register. Uh, that's
35:22the stack pointer. Eventually you can learn how to manipulate the stack pointer in order to create
35:27your own local variables. That's okay. Once you know what you're doing, but unless you know exactly what
35:32you're doing, you probably shouldn't touch the stack pointer, especially you shouldn't mess with it
35:37right before you call a function or right before you return from your own function. Be very careful
35:41with that. The RBP, uh, register is similarly dangerous. Uh, it won't like automatically destroy
35:49your program, but it is, uh, it's usually, I mean, it's quite often used as sort of like a bookmark for
35:56where the stack pointer was pointing, uh, in, in, in other modules and other functions. So if you
36:01fail to preserve the base pointer, the RBP, uh, and then you returned from your function,
36:06then you might have actually messed up the stack pointer for the caller or some other caller somewhere
36:12in like the ancestry of your call graph. So be very careful. I mean, I guess all Kali preserved
36:19variables or, or registers you should be very careful, careful about, but these are like kind of
36:23the two worst ones, uh, to forget about. Um, there's also another register that is not listed
36:29on this page called the, uh, instruction pointer. It's RIP. It basically is a register that holds
36:34the memory location of the next instruction to be executed. So if you modify that, then you just told
36:39your program to go execute in some random crazy place and probably everything is going to crash.
36:46R10 and R11. I can't remember already if I explain this, but I'll just say it again. R10 and R11 are just
36:55temporary registers. You don't have to preserve them. Uh, but the callee, if you call another
37:01function, they don't have to preserve the registers either, which means it can be really fast to use
37:06R10 and R12. If you are kind of like already running out of registers, uh, if you don't have to
37:13make a function call anywhere. So, you know, no one is going to ruin your R10 and R2 or R11, then you can
37:20also use R10 and R11 to sort of like, you know, reduce your hits to system Ram to speed up your
37:26program. So R10 and R11, they should probably be used last R12, 13, 14, 15. Uh, I usually use those
37:33first. Once I start running out of those callee saved, then I'll start eating into the, uh, the
37:39arguments. I'll say, you know, I'm going to start using RBX and then I'm going to use RDI and RSI and
37:44whatever. It's okay. If you use, you know, the argument registers, um, as long as you've already
37:50done something with your incoming arguments or you don't have any. So just keep that in mind.
37:54You're allowed to use them. I mean, this is all just a standard. As long as you obey what the rule
37:59is, then you'll be all right. The rule is you got to callee, save those. The rule is that one's just
38:04temporary. The rule is this one's an argument and so forth. Okay. So I think I've mentioned,
38:14a lot of stuff about, uh, locals and how we use these registers. Um, let me try to just show you
38:21one more thing real fast. Uh, so I said before that the size of the REX register is 64 bits because
38:29it takes up, uh, the whole, uh, available register bits in the CPU, but there are other versions
38:35of this register that we could use. What if you only wanted to use 32 bits, uh, in your register? What
38:42if you wanted to pack like two different 32 bit numbers in the same 64 bit register? You could do
38:46that. There are also older modules and instructions that will operate only on 32 bits. So how do you,
38:52how do you reference only 32 bits of a register? Well, if I search for, uh, oh shoot, what was it?
38:58Is it EDI? No, no. Shoot. It's in here somewhere. Uh, this is like a good lesson in actually preparing
39:19before you record a video. So I'm going to do architecture overview, CPU registers.
39:31Oh, it was at the beginning of this section. I always skip down to two point or 12 point something,
39:36and I should have gone to a 2.3.1.1. Okay. So obviously I'm not going to remember all of this,
39:41but basically notice at the top, we have the REX register, uh, all 64 bits of it. But if you wanted
39:49to access the lowest six, uh, sorry, the lowest 32 bits, then in your code, instead of putting RAX,
39:56you put EAX. So for example, um, you know, move, uh, RAX, whoops, RAX, let's say the number five,
40:05but over here, we're going to move EAX, the number five, uh, here, it's going to set the whole 64 bits
40:13to just like a bunch of zeros. And then there's going to be a few bits that help represent five,
40:17the lowest bits. So it's going to erase like the whole thing, all 64 bits. But if you use the EAX
40:24instruction, then only the lowest 32 bits will actually be modified, which means whatever data
40:28you had in the higher 60, uh, the higher 32 bits of the, uh, of the register itself, they're just
40:33going to stay there. It's going to be considered junk data at that point. So sometimes if you're going to
40:39work with something that's going to be using only 32 bits of your register, you better make sure that the
40:43other bits are, uh, are cleared out if you intend to use RAX right after that and vice versa. So keep
40:51in mind that we have other forms of the same register, a designation, but just to let the CPU know that
40:58we only want to touch 32 bits worth. We only want to touch 16 bits worth or eight bits. And this, this
41:05kind of table covers all of the registers, RAX, RBX, RCX, and so forth. I think it doesn't cover the
41:11instruction pointer and something else in there, but you know, most of them. Um, however, you know,
41:18for my purposes, I usually just use 64 bits cause I'm not like super advanced unless I have to divide
41:23or do something. Okay. So that's the last thing I wanted to mention about the general purpose registers.
41:29Um, let's see. Callies saved. So the, not sure if I said this before already, but basically the,
41:50so just to emphasize the, uh, the RBP register is the base pointer register. You're probably going to mess up,
41:55uh, the caller because the caller often uses RBP as a bookmark for the stack pointer. You definitely
42:01don't want to mess with a stack pointer unless you actually know what you're doing
42:04in order to create local variables. Um, there's another variable, uh, another register in here
42:09called RIP, which is the instruction pointer register. So if you, if you mess that one up,
42:14then the program is going to start executing in some crazy place that doesn't even make sense.
42:19Okay. So now that we've talked about general purpose registers, by the way,
42:22I should have mentioned before that, um, RBP and RSP, they're typically called, uh,
42:29special purpose registers, not general purpose. General purpose is like RAX, RBX,
42:33all these ones that you can just randomly use as long as you respect the ABI. Uh,
42:37but the base pointer and the stack pointer, those are kind of special. And the, the RIP,
42:41the instruction pointer, that's a special register. You shouldn't mess with those unless you really know
42:46what you're doing, uh, or you'll get, uh, you'll get yourself in trouble. But, um, now that we've
42:51talked about those, let's talk about the floating point registers just real fast. Not going to talk
42:55too much about, uh, using floats until a lot later, but basically, uh, I at least want to mention that
43:07we do have registers that will work with floating point operations. Let me see. There we go. Okay.
43:12So it's 18.2 floating point registers. Um, essentially we use, uh, XMM zero through XMM 13
43:22for floating point numbers. There's not going to be any, uh, crazy letters like RDI, RSI that you
43:27have to memorize. You can just use zero through 15 and it's totally fine. When you want to return
43:32a floating point number from a function, you will load up XMM zero as the return value. You won't even
43:38touch RAX. The arguments are the same. They're just kind of ordered. It's like the first argument
43:43is going to be XMM zero. The second argument is going to be XMM one and so forth all the way up
43:47to XMM 15. If you need more than 15 arguments, you're going to have to do some, some sorcery with
43:54the stack or with, uh, some kind of system like the global variable or something. And also for floating
44:01point registers, we use different instructions. So what I really want you to know, cause this is mostly
44:05about the regular registers is that, um, if you have floating point numbers and you want to multiply
44:10them or do some other operation on them, the normal instructions for regular registers won't actually
44:15work. You'll have to look up a different set of instructions. For example, and I'm going to do this
44:20in another video some other time. Uh, if you want to move, uh, data with a floating point register,
44:27you can't just use the regular move instruction. You got to use either move SS or move SD.
44:31Um, and, and both operands have to be floats or actually I think the one on the right can be memory,
44:39but, um, move SS basically means a single piece of data, single precision SD means single piece
44:47of data, double precision. So you can move like multiple pieces of data at a time. I'm not going
44:52to really cover that. Um, but you can move single precision floating points, which means they're 32
44:57bit floating points, or you can move double precision floating points, which means they're
45:01a full 64 bit floating point number. There's also bigger registers, but I'm not going to talk about
45:06that. Yeah. Well, yeah. Later processors 256. I think mine has that, but I'm not going to talk about
45:12that. I think mine are like YMM or something. So yeah, with that, uh, those are the basics of registers and
45:20some stuff to keep in mind in future videos. I'm going to talk more in depth about making functions and
45:25calling functions and, and all that stuff respecting the ABI. But, uh, for now, uh, here is your primer.
45:31I hope it was useful on, on CPU registers. Thank you for watching this video. I'll see you in the future.
45:39I hope you had a little bit of fun and you learned a little bit of stuff.
45:47Hey everybody. Thanks for watching this video again from the bottom of my heart. I really appreciate it.
45:51I do hope you did learn something and have some fun. Uh, if you could do me a please,
45:56a small little favor, could you please subscribe and follow this channel or these videos or whatever
46:02it is you do on the current social media, some website that you're looking at right now. Um,
46:06it would really mean the world to me and it'll help make more videos and grow this community. So
46:10we'll be able to do more videos, longer videos, better videos, or just, I'll be able to keep making
46:15videos in general. So please do, do me a kindness and, uh, and subscribe. You know, sometimes I'm
46:21sleeping in the middle of the night and I just wake up because I know somebody subscribed or followed.
46:26It just wakes me up and I get filled with joy. That's exactly what happens every single time.
46:30So you could do it as a nice favor to me or you could, you could troll me if you want to just wake
46:34me up in the middle of the night, just subscribe and then I'll, I'll just wake up. I promise that's
46:38what will happen. Also, uh, if you look at the middle of the screen right now, you should see a QR code,
46:44which you can scan in order to go to the website, which I think is also named somewhere at the
46:48bottom of this video. And it'll take you to my main website where you can just kind of like see
46:53all the videos I published and the services and tutorials and things that I offer and all that
46:57good stuff. And, uh, if you have a suggestion for, uh, uh, clarifications or errata or just future videos
47:07that you want to see, please leave a comment. Or if you just want to say, Hey, what's up, what's going on?
47:11You know, just send me a comment, whatever. I also wake up for those in the middle of the night.
47:15I get, I wake up in a cold sweat and I'm like, it would really, it really mean the world to me.
47:20I would really appreciate it. So again, thank you so much for watching this video and, um,
47:25um, enjoy the cool music as, as I fade into the darkness, which is coming for us all.
Recommended
23:01
13:50
2:52