 All right. We are back. It's Monday. Hope you're all working feverishly on the assignment. Has everyone at least started the assignment? Head's not unanimously. What assignment? Very good joke. You go on Friday. You don't know about it. I don't know. Get caught up. Okay, we'll probably go through and look at some stuff while we're from the Dojo. We'll probably go and maybe look at some challenges as we go and cover material here. I think today we should go up and have everything that we need to. So, all right. Let's go right where we started off. We started off on controlling, we're talking about memory. So, where were the different places? So, you're a CPU. Where can you get data values from? Registers. And what could you do with data in registers? Yeah, arithmetic operations, more generally. What's that all? Logic. Okay, that's too high maybe. Looking for compute, right? But yes, you can operate on that data. You can do stuff. Arithmetic operations. All type of operations. Awesome. Okay, where else can you get data from? Stack and heap, which is located where? In memory. Yeah, so we can get stuff from memory. We also talked about we can get stuff from the disk and from the network. But at the end of the day, all that stuff comes back to memory. So, okay, we talked about on Wednesday the syntax for accessing values in X86 syntax from assembly. So, that's the brackets here. So, what does this instruction mean here? Move into RBX bracket RAX. What's the semantic meaning there? Yes, perfect. Okay, so move, go, treat whatever's inside RAX as a memory location, right? Somewhere in memory. Move those 64 bits, those eight bytes into RBX so that after that instruction executes, RBX will contain what? Will contain the value one, two, three, four, five? In hex? No, why not? Yes, why yes? Yeah, we don't know what's stored in that memory address, right? We don't know what bytes are actually at the memory location, hex one, two, three, four, five. Could be anything. What if that memory address doesn't exist? Yeah, it's our old friend, the seg fault, which I'm sure you've seen in your C and C++ programming days, right? So, that's technically triggered by the operating system, but essentially it's a way to say, hey, there's nothing at that memory location. Okay, then so that's loading data from memory into a register. And then we can load memory from a register. Wait, this is loading from memory into a register and then we can store values from a register into memory. So, this is moving whatever's inside RBX to the memory address one, three, three, three, seven. We can also use RSP and RVP like we talked about as memory addresses because they should, if everything's set up properly, RSP should point to the start of the stack. And so we can use this to do pushes. Now, the question is, all these examples are writing eight bytes to and from memory. Why is that? Are registers are eight bytes? Yes, and specifically which register in, let's say, this first example of move into RBX, bracket, R-A-X. RBX, yes, because we're saying we want to store from that a memory address and we want to fill up all eight bytes of the RBX register. So, that's why it goes and fetches eight bytes. We can tell it that we actually want to read or write smaller values for memory. You can look it up of how to actually specify this precisely in the instruction syntax. Here, because we're using the no prefix option, means we don't have to specify this and it will infer. So, for instance, this last instruction here, move into wherever RBX points to EBX. This will only store 32 bits or four bytes. And finally, this last example, now we're loading eight bits or only copying one single byte at that memory address. Can we copy or can we copy less than eight bytes into memory? Yes, how? Yeah, so the lowest we can get is like BH and BL, which is still one byte, which is eight bits. How do we do four bits? What's the memory address of? So, if I want to only fetch, let's say, four bits from memory address one, two, three, four, five, how do I say I want the upper four bits or the lower four bits? Move from high, move from low or both one byte, which is eight bits. So that would move like this example here is the the EBX registers the high byte. BL is the lower eight bits, which is also a byte. Yeah, but let's say I want I want only the floor. Can I load only the floor? Shifting, we can only shift when that is inside of a register. So the data has to get from memory into our register first. You don't think you can do it? Why? Yeah, so maybe is this memory got memory? Okay. So if we go back, what does each memory address indicate? One byte, which means we can't address anything smaller than that. We can't say, Oh, I only want the lower one bit of this byte, you'd need actually further addresses, right? It's like, think about a street address, right? And at a certain point, you can't subdivide and say, Well, I want this specific bedroom in my apartment letter to go to, right? You may be able to specify the city or the state the city, the zip code, the address, the apartment number, but you can't say I want it to this specific bedroom. I guess you can but UPS is or USPS is not going to deliver it there. So the idea is that every memory address references one particular byte in memory, and that you fundamentally cannot reference a smaller unit. So that that was the point of that exercise was to reiterate this. Cool. Okay, so we can't go smaller. We can always we can always deal with smaller data. We can bring in eight bits and shift it whichever way we want to only deal with a single bit. We can always do that. But fundamentally, when we're accessing memory from the we're fetching them from memory, we are always going to be fetching at the least a single byte, which is eight bits. Everybody cool on that? Alright, and going back to the question on Twitch. They're asking about this push push RCX. There's a subtraction from RSP eight before we move. And the idea is, these two instructions are equivalent to a push RCX because a push RCX sub fundamentally subtracts from the stack pointer and then pushes whatever's at RCX onto the stack point. So just showing that those are equivalent. And you have to subtract before you move because that's the semantics of the push instruction. Cool. Okay, so size of the right so we can control now we can write at the minimum one byte at the maximum eight, eight bytes into memory. And depending on what we use as our source or destination register, that controls what we read or write. Cool. Alright, now becomes the most insane thing maybe that you've hopefully seen before. I don't know, for me, this is one of the weirdest things about architectures. The question is when we say in memory that we want to write eight bytes. So in this example here, I should do really want to draw but that's okay. I will resist the urge. Okay. So let's go here. So the question is when I write, let's say 32 bits, so we'll use bytes because it's easier to think about in terms of bytes. Why because of memory, we can't go smaller than a bite. So this example here, I am moving into ebx from ebx. So four bytes into RAX for RS is this memory address 13337. So I should be able to draw a box with all the bytes of RAX. And I'm going to figure out how to do this. Okay. So are we going to draw on Google Slides? Maybe no. Maybe a good solution, maybe just hop into a zoom meeting by yourself and annotate on your screen. Yeah. This is why I like PowerPoint because I can draw with my finger. Okay, but that's okay. Let me, the problem is I don't want to bring anything up and actually have anything. So I just pause streaming. You could hide your screen. Oh, sick. Okay, good. Alright, don't show them what I'm showing you now. Not freeform. What's the vision app I use for this is here. Note shelf. Sweet. Hey, that worked. And we're back. Yay, we can write. Okay, cool. So I want to only deal with four bytes because it's the same with eight bytes. So we have the register RBX. Okay, RBX is going to have how many different bytes in it. Yes, this is the best diagram you've ever seen in your life. How many? Eight. Okay, but we're only concerned with which four, the upper four, the lower four, we're talking about EVX. Lower four. Okay, one, two, three, four. And I will use a very handy technique of putting dot dot dot to so that you can fill in the rest. Okay. So let's say we have the value. Oh, one. This is all hex. Oh, one. Oh, two. Oh, three. And Oh, four. Right. So this would be, please switch the text. Okay. Zero X, Oh, four. Oh, 302. Oh, one. Can you read that? No. Increase font size. Show fonts. Giant. White text is very much not appreciate. All right. Cool. Okay, so this is the memory value. This is the value inside EVX. Everyone agree? Okay. Now we have some instruction. So EVX. So let's say we just did a move into EVX. See, this is why text editors are just fundamentally better. Okay. Okay, so we're moving that value to EVX. So now we know that value has to be EVX. We also know because of stuff that we've looked at if you move something into a 32 bit register like EVX, it zeroes out the upper bits of RBX. But that's not relevant right now. I just wanted to re remind that. Okay. So then let's move into I only want to do memory address 10 because I don't have to write this out by hand a little bit. So we want to move at this memory location 10. We're going to move EVX into there. Right. So what we just talked about how many bytes are going to get get written at memory address hex 10. I mean, four, four bytes, which ones, the lower ones, one, two, three, four, right, this value. Okay. So this happens. So this has been executed. Now the question is in memory. So if we draw our nice memory, we have memory address 10 x one zero. And what's one after that? One one, and then what? One two, one three, what are the sizes of memory addresses that each of these things? What's the size like what's in? So I'm going to draw a box here. Right. What's the size of this box? So a single memory address corresponds to what? How many bytes? I've heard every number. One address is one byte. So each of these boxes represents one byte. But I just asked the computer to write how many bytes to the memory address 10. I mean, four. So which way do they go? Which one do I write first? It seems like I should just do what? Oh, four. Oh, four here. And then Oh, three. And then Oh, two. And then Oh, one. Is that? Yeah, the question is, do I go from higher bit to lower bit or lower bit to higher bit? And the thing this is what I said that is surprising is different CPUs do this differently of what this actually means. So if you're looking at a value in memory, and you're looking at four bytes, and they are 04030201, sequentially like that. And sequentially here means, at a memory address, one after the other, you have to understand if you just were looking at memory, you'd have to understand, does that represent this value? Or does it represent the opposite of the flipped one, 010203 or four? And you need to know something about the specific CPU architecture that you're operating on. And this is why we go back into and this property is called the endian this, like which side is the most significant or least significant byte store. And to be perfectly honest, I can literally never remember which one is which I, that's why I'm coming back to this slide to go through this example where hopefully it's all correct and I can teach you the right way. But the point is that it just depends on the, yeah, so most things actually store it backwards. So this I did remember that modern systems are what you don't expect. And that's what little endian is meaning the bytes are flipped. So the, so for instance, here we have removing cool cafe CO01CA75 into EAX, four bits or four bytes, sorry, four bytes into EAX, moving memory address 10,000 into RCX. Now storing that value into EAX. And the way we drew it, right? And this is actually the register EAX. So we have C0 01CA75. But we wrote it out was it just writes it exactly like that. So that this would be at 10,000. This would be a 10,001. This would be a 10,002, 10,003. That is big Indian format. However, most modern CPUs use a little Indian format and they flip it around so that it actually stores 75 the lowest byte at the highest point at 10,000 CA at 1,001, 01 at 1,002 and C0 at 1,003. Yes. So if we go back to our original example of 04030201, yes, the big Indian approach would just be 04030201. Little Indian goes 01020304 yes. Little. Okay. And then the big Indian approach, something like this would be 01. This is ruining my masterpiece. Okay, 01, 02, 03, and 04. And then if we asked for, hey, this memory location. So now if we had a move into ECX, hex 10, move, please. I don't know why this is moving. But if we had this, we're extracting then in big Indian, from memory address 10, move, load that into ECX. What's the value of ECX after here? It's a little bit, because the example is 04030201. Yep. So the little India would be 01 at the top. Yes, you would think that, but pointing here, but this all happens transparently to us, the programmer. So this actually gets flipped back when it's put into the register. So when you copy the memory back, it takes whatever four bytes are here, and says, Oh, this is the most significant byte that goes here. This is the second most significant byte that goes here, this goes here, this goes here. So it actually puts it back and the value inside of RCX will be for hex 4321 or 04030201. But this is important because if you were to then say, so in both architectures, little in big Indian, we're using the same data types, it doesn't matter because the CPU is fundamentally doing this flipping for you. But where this can get changed. So now, if we were moving, why can't I move you? Oh, God, this is insane. Okay, so if I were to move into, let's see, DL, so the lower byte of D. So now I'm moving from this into DL in little Indian, what's the value of DL going to be 04? Why because I'm a little Indian? Well, on both of them, it's just gonna if you're only addressing one byte, does Indian this matter? No, because it's just right. It's only if multi byte things that it has to figure out the order. But if you're only asking for one byte, then what matters is it matters in the sense of whatever you wrote there changes the layout. So and a little Indian format, DL will be four, because that's exactly what's at 10. Whereas this exact same code on a big Indian format, DL will be one. And to make this even, oh, yeah, go ahead. Fantastic question. I have literally no idea. It must have some hardware benefits that I don't fully comprehend or understand. But I'm sure there is a reason. I'm sure there was a reason at some point. Yeah. No, so wait, did I write that wrong? Oh, you may be. Yeah, let's see. So seven. So the lower goes out the Yeah, good call. Look at that. Somebody was paying attention. You're all fired. Just kidding. See, like I said, I don't actually remember which way is which I just kind of Yeah, there's definitely can be some benefits. But I don't know if those are necessarily widespread. The other weird thing to actually make it even more confusing. So like I said, most CPUs and processors use little Indian format. So you think why do we even have to learn about this? Like why not just learn about little Indian? The fact is a lot of network protocols. So actually use big Indian format. So when you're looking at a network packet, you actually have to realize that it's a different way that you're looking at things in memory. And your computer is actually doing flipping of bytes around to change it. So it's definitely something that comes up there. Alright, so oh, and the other thing is, where this comes into play a lot. And we'll I will try to remind you in the future when we get there, if you're let's say, overflowing memory, or corrupting something in memory, and you want to put a memory address at a very specific location. And you're writing it, typically, you'll have a sequential write, like you'll start at 10 here, and then write that byte and then that byte and then that byte and then that byte and that byte and that byte. But if you're writing a memory address, you have to remember that it's flipped the way that you expect. So if you put a memory location in the wrong way, it'll get interpreted opposite. Anyways, you'll we'll get there. You don't remember that for now. Okay, cool. Okay, so, okay, yeah, so this is one one theory, I guess there's a Wikipedia article you can learn. Obviously, we repeat article about everything you can study more about this. Cool. Okay, so you can also do some cool stuff with you can't you don't have to always just get memory directly from a specific value in register, you can actually perform some computations on that value. So for instance, you can and this is pretty limited to the initial address. So in this case of the RSP, usually plus some offset like five here, and then plus another register times a value, we'll talk about why in a second. But so here we're moving zero into rx. And then we're moving from the stack, plus rx times eight, whatever that memory location is moving that into our vx incrementing rx. And now, where does the next one? Where is this next read going to happen? RSP plus what plus eight. So why would you want to maybe go over things so you can think of like, if you're writing like a for loop, rx here would be your iterator, your eye counter. RSP would be the here the thing that you're pointing to the start of it. And you're going through. And you can have with the same expression here copying a certain offset of that into a memory location. Does that make sense? So this will help you again, increment. So that's just plus one. Yeah, very simple. Go back to the other slides and look it up. But yeah, adds one. So that would have helped to describe what this was doing. So thank you. Yeah, so here it's rx times are zero times eight. So zero. So it's right at RSP. And then the next time it's RSP plus eight. So in this case, you have two 64 bit values in memory, one at RSP, one at RSP plus eight. And so you're copying them into you can have n number of RSP or sorry, n number of 64 bit values in memory. If you have a pointer the first one, and you know how many to access, you can use this pattern to go through them sequentially. The stream not great. How many kilobits per second does one stream need? Okay, okay. Sorry, don't know what to do about that. Oh, I guess. If somebody remind me next time, I'll try to figure out how to get internet in here on Wednesday. Okay. And then okay, so the other thing you can do is and what you'll actually see is this Leah address or load effective address. This essentially does the math without the D reference. So this is the one time where it's kind of crazy that you actually see a bracket syntax but no memory is actually fetched. And what this means is Hey, in the destination, put whatever whatever that address would be that you would have accessed. So for instance, if here, RX is one, popping a value into RCX, and then saying load effective address RSP plus RX times eight, so RX times eight is what? Eight. Thank you plus five. So this would be eight plus five plus RSP. And that would be what's inside that, that value. So we're not dereferencing memory here, but we're putting that value inside of RBX. And then we're dereferencing RBX and moving it into RVRBX here. Like we said, there are limits, you can just do a register plus a register times either two, four, eight plus a value that's limited by the instruction set. But that does give you some aspect of what you can do here. Cool. Okay, cool. So some cool things that you can do is access. So we mentioned you can't directly set the RIP register, we're gonna see next how we can actually manipulate where we're executing. But with load effective address, you can actually do this. So you can set RX to be whatever the current of the next sorry, the next instruction pointer. So important thing to remember is RIP always has the value of the next instruction to execute. You can add things to it like we saw. So eight plus that we can actually you can actually read those bytes if you wanted to see what the next instructions were to be executed. And we can actually even write there so you can overwrite the instructions that you're going to be executing. This is so you can do some insane stuff of, you know, you can have self modifying code so you can have code that modifies and changes itself as it's executing. And this is actually pretty important. Cool. Okay, now, this is what we had to so previously, we didn't have to specify any sizes of our operations, because the assembler could figure out, Hey, you're moving from ebx into some memory location. I know that's four bytes. But if we have an immediate value that we're writing directly into memory, so in this case, we want to write the 32 bit value 0x 1337 to this memory address. We have to tell the compiler, Hey, this is a d word pointer, or a d word. This means that like, that I want to write 64 bits, because how can they are 32 bits? Sorry, q word, a quad word would be 64 bits. Because how does the assembler know that you want to write 32 bits if you only give it a 13337 value? And we talked about kind of memory. So we've kind of treated it this big space. Other things can be mapped into memory. So we'll get to them later. But yes, anyways, as you allocate memory, if you using malloc or free, this is how you can get additional memory from libraries and eventually from this CPU. But we won't go into their right that control flow. As anyone, everyone written in if statement before. Yeah, cool. So we all understand right that we want computers that are able to change their decisions based on data or based on calculations, right? So what we've been doing right now under the hood is just moving data in and out, calculating it and then moving it back. But we wanted to execute different types of code depending on what's happening. So this is what so now you're going to actually look at underneath the hood, how are branches and decisions done at the CPU level. So first thing to do is to a brief, like, looking really under the hood to say like, how does the CPU actually work? So remember, you're writing assembly code. Can the CPU just directly execute your assembly text file? What is it execute? I need some bytes, right? It needs some instruction needs those instructions encoded at bytes that it can understand to then start executing on. And so this is so you can think of this is I don't want to zoom in but you can think of so this here is memory, right? So just like this is the nice thing about kind of modern architectures is your memory includes your binary code. So your the code that is actually executed those bytes that the CPU interprets to do whatever it needs to do. Those are laid out in memory. There's an area that you're dynamically allocated memory. So when you call malloc and free those kind of things memory ends up there. There's usually you use a library. So when you call printf, right, the operating system actually has no idea what a printf is there's the libc like the C standard library that implements a printf function. So that code is there so that your program can actually call into that library stuff happens and then it comes back. You then have your stack. So this is where your stack memory kind of lives. And then there's like OS higher levels and all kinds of stuff. So anyways, but looking into your binary code, you know, you wrote some instructions like pop rax pop rbx add rbx to rax and stored into rax then push rax. And again, this is compiled down to binary at memory are these specific bytes. So at memory address 40,000 hex here are the bytes 558. And that corresponds to a pop rax. At 4001 is 5b at 4002 is 4801 d8 that corresponds to this ad and then 50 with this push rax. So I'm not seeing are we way behind on the stream or something. Oh, that's okay. Okay. Okay, so, so the CPU. So basically everything gets loaded into memory, the operating system tells the CPU, okay, everything's good now start executing at bite 40,000. And the CPU just figures out what's there and starts executing it. So in this case, it would just execute pop rax. And then the next thing it would do would just be the next instruction. It at 4001 and it would just keep there's a cycle for this that I think it's like the decode execute update loop in architecture. But anyways, it doesn't matter. It's our fetch decode execute. But anyways, fetches from memory what instruction is does it and then updates the instruction counter to the next one. So this is just going through doing all of these, these aspects. And what we are concerned about here is now what we want to change that. So we want to implement a branch condition that says, hey, if this then go somewhere else, or we can just say go somewhere else. So, so CPUs will just keep on executing. And you're implicitly doing this as you write an assembly program, you write the first instruction, and you just know that, well, the next thing that will happen is the next instruction and then the next instruction. So with a jump instruction, we can tell it to go somewhere else. I highly recommend using labels, because it's a pain in the butt to be writing assembly code and manually specifying how many bytes to jump forward and backwards from. So this is what your assembler is for and very good for. Here, we can do something of move. Why is that not RCX? That should be RCX, I think. Whatever, move 1000337 into CX, then jump to stay lead. And so this all caps means is a semantic thing that means it's a label. I don't think there's any requirements on what if it has to be all caps, but there should be somewhere in your assembly program that is the label name and then a colon. So this actually is the same in C syntax, you can have C has a go to statement that works very similarly. So you can actually do the same thing and see, but here. So this jump statement means is start executing right at the label stay lead. And then the CPU when it sees that changes execution there, does the CPU know anything about labels? No, so your assembler has to figure out it does its job of assembling everything, all of your instructions into hex, and then it figures out how many bytes this jump should jump forward. But you should not worry about that because that's so annoying. You should just say, jump to stay leet and the compiler figures out, aha, this move zero into ECX is four bytes. So if we look at this, we have our binary code. Move that into ECX jump to stay leet, which was our original instruction, zero into RCX push RCX. And then so then looking at the bytes, we have our first instruction, which we don't really care about this next one. So you can decode this by hand, EB is a jump. And then the next one is how many bytes to jump forward. And the important thing is this, this is an offset of the next instruction. So because it's not four bytes from the start here, because that would be two, three, four, which would pop in here, right at the zero, zero, it would be EBO four, skip four bytes, means to skip over these next four bytes one, two, three, four, and then start executing at 51. So when this push RCX is executed, what's the value inside RCX? Think about that while I try to fix the stream somehow. It seems bad. Pause button, pause streaming. It's on my CPU. That looks fine. Drop frames. That's a lot of drop frames. Okay, well, it's recording locally. So I don't believe you, but and then go to output. What's the, what's your feelings for streaming? 6,000 changes of 25. Should be enough for anyone. I don't want to go to the library to restart it. Wolves, the stream keys busted. That's okay. Well, we only have a half an hour. So I'm definitely not fixing. And I don't want to stop the recording to fix this. Oh, I guess I could disable this. And it's just so dangerous. Oh, but you'll see the string key. What am I doing? I stopped the video. So how do I do I just log in to Twitch and get a new string key right now? That seems insane. Did somebody leak it? Oh, there we go. What is this thing is garbage? We use the software. Okay. Hey, can you see us now? Are you watching the stream now? This is the problem. It's going up and then coming down. Okay, at least orange, orange green. This is still like way more kilobits per second than it ever was. But sure. Cool. Okay, so back to the control flow jump. Oh, you can't see this. People want everything. I see the screen, they want to see the stream. We can all see. Cool. Okay. What was the question I asked you because I completely forgot. Yeah, what's thank you at this push RCX. After the CPU executes this, what value will be pushed to the set? 1337 Why isn't it zero? We skipped it completely. We jumped over it. We said we don't want that anymore. It's fine. Let's just skip it. Great. Okay. Now, does this can we use this jump to implement our if statement? So how does this jump work? It's unconditional, which specifically means we can't use it for condition. So this will always jump to the next place. So we need some other types of jumps, right? So we need conditional jumps, we need all types of jumps. So so jumps, we can have jumps that rely on conditions. So we can say things like, there's all these conditions. It's kind of, I don't know, this definitely does confuse me. But there's jump if equal, jump if not equal, jump if greater than or sorry, jump if greater, jump if less, jump if greater than or equal to greater than equal to less than or equal to why would these not be moved around? If above or below or above or equal below or equal signed, not signed, how would you tell if it signed if something is signed or not signed? Yeah, there's a leading one if the highest bit is one. If there was an overflow or not an overflow on an arithmetic computation, if zero or if not zero, so we can have something like this and we can say jump if not zero. But the question is, how do we actually like if what is not zero, because if we look at this instruction, are there any registers to test in there? What's the argument to this jump if not zero instruction? Yeah, so specifically at the low level, it's how many instructions to jump. But specifically here, it's the label, right? So it's not a register. It's not telling it, hey, look at this register. So we actually have to use another thing for that. So on the so excuse me. Okay, so there are I think we talked about this maybe when we talked about specifically like the registers and everything, but there is a there's actually a special register called the flags register. This is that our flags. You can't directly access it, but it keeps track of different flags. And that's technically what you're jumping on. So if you really want to get down to the weeds, this like zero F, ZF is this is a flag in the flags registers, sign flag CF, you can kind of ignore that a little bit. But essentially, the idea is these flags are updated implicitly by the CPU. Every time there's some arithmetic operation. But that is very rare that you'll see code that relies on that much more common is you'll see the comparison instruction comp or test. And the conditional flags that you'll look at are the carry flag was there an extra 65th bit, zero flag overflow flag sign flag. So anyways, digging down, we can do things like compare RBX and RAX. Yeah. So is it the case that when it comes to maybe the ZF flag, it's not that there's a ZF flag that you use to indicate one or use zero. It's just there's a ZF flag and it's either one or zero and you check that state. Exactly. Yeah. So the exactly. So literally this jump if j e flag or j z flag, I think is the better one here, the j z flag will jump if the zero flag is set. And this flag is just set implicitly by the CPU as it's doing computation, depending on the last thing. So usually, when you looking at code, you'll see a comparison, which will set those flags and not actually do anything. So for instance, compare RBX, RAX, and j a so j a jump above on side. So there's an unsigned comparison. So it's not caring about the first bit and it has no conception of negative numbers. And it's checking if the unsigned RAX is greater than RBX. That's just what it does. And if that's true, it will jump if not true, it will continue executing after it. Similar thing, compare RAX RBX, jump if less than or equal to this is a signed comparison. So it actually does keep track of signed if negative values will be less than. So in this case, negative all all f's is negative one that is less than zero. So signed comparison of RAX less than or equal to RBX, test RAX RAX. So this is RAX compared with itself. So jump if not zero if RAX is not equal to zero, jump if equal, compare them and jump if equal. So you can do all these kinds of checks. And this is literally how your compiler depending on your if conditions, this is literally how your compiler compiles down conditional checks into assembly. Cool. Okay. So and with this, we can implement loops. So have you used loops in high level programming language? Please don't. Thank you. Okay. So we can do four loops or while loops, but there's actually no additional mechanisms underneath to support that this is what's really cool is you as a C programmer C++ programmer, you don't have to know that the CPU doesn't support a while statement or a for loop, because the compiler outputs x86 code that implements that, but it doesn't exactly the same way as the semantics of the high level C code. So that to you, you don't know that there's a difference and you don't know that that doesn't exist. So for instance, we can do a loop that counts to 10 by setting zero to RAX. So at the start instruction that always has to execute zero is RAX. Then we increment RAX. What does increment RAX do? By how much? Yeah, increases the value of RAX by one. See, you just learned something today. You'll never forget. Then we compare RAX with 10. And then we do JB. What's JB? Jump if below. So jump if below. So if RAX is below the decimal value 10, we go where? Back to the top at the loop header of the label. And then we increment RAX again. Now RAX is what? Two. Then we do the comparison. Where's it going to go? And then the jump JB, which is the JB? Yeah, so but what is it? Are we going to jump? Yep, go back. And we'll keep doing that until finally, when we we, this is not true, RAX will be 10. Is that right? Jump if below. Okay, yeah, so it's not below because it's equal to. So it should be 10. So you see, we've actually implemented a loop by jumping backwards. And that conditional loop that condition is the statement. Do you not use branches? Can you elaborate on Twitch? Because this jumps are basically branches. So a conditional jump, you can think of as a branch that you normally think about. Say it again? Oh yeah, for sure. All this stuff. Yeah, that's a good point. So the CPU when it gets to this like JB or any conditional branch, it doesn't know where the two so when the CPU gets here, where are the two possibilities that it can execute next? One is right after it if the condition doesn't hold, where's the next one? Back at the label at loop header, right? And it actually doesn't know exactly which value that's going to be. So CPUs do a lot of crazy stuff, in that they actually keep track of how many times of loop went in a certain direction. And so they will start specatively executing in that direction. And then if they're wrong, they flush all those instructions out of the CPU pipeline, and then start executing at the other one and take a performance in. But if they're right, then they've already pre executed a lot of instructions. So anyways, there's a lot of crazy optimizations that happen under the hood, but you shouldn't have to ever know about that at this level. Cool. Okay, so let's Okay, what do you think? Should we do an example of this? Yeah. Well, we're almost done with the slides. We'll do slides and then an example. How about that? That will do system calls. That's actually great. Okay, I like this. Cool. Okay, so other things is so you've written code in other languages. Do you typically write functions in those languages? Why? Why not just have one public static void main that does everything stuff can be reused? Yeah, excellent. Why else? Inheritance. What was it? Inheritance. Again, reuse, right? You may want to reuse it in other contexts. Yeah. It's a nightmare to debug if it's all in one thing. Yeah, debugging or testing, it can be easier to test a single function. Yeah, easier to read. There's a famous quote by actually don't know who this is by I want to say was one of the C authors that they were asked how do you know if a function is too long and should be split into multiple different functions. And he said, well, I put my head up to the monitor. And if the instruction is longer than if the function is longer than my head, then I split it into two functions. No, I don't know what font size so I can't contextualize that joke because with your insane font sizes now you can do that. This is on old CRT monitor. But the idea being, you only have to reason about the context of whatever that function does. And then when you're if you're reasonably sure it's correct, but you can move on and look at other functions and then start testing them that way. So functions are great. We love functions. You can actually write functions in assembly. Yay. In fact, this is how the C compiler compiles your code down from your C function has a corresponding function call and function definition at the assembly level. And okay, so you do this with the call and ret instructions. These are the super important ones. Call is a function that or as an assembly instruction that takes an address. So the address of the function to call, and then pushes a bread crumb on the stack of how to go back. Rhett does the opposite of this pops whatever is on the stack into RIP and starts just executing there. So we can for instance, here's a function check delete that has a parameter off. We'll talk about specifically how parameters are passed. That actually depends a lot on the operating system. And it's a specification. But for now, let's define this. So we have, let's say up here at the top is our main function. So we're going to pass arguments in through the first argument through RDI. So the first thing we're going to do is is from the main function set RDI to zero, then call func check lead. Then we will start. So the call function will store on the stack, the location, the address of this next instruction, and we'll start executing at func check lead. So this starts executing it tests RDI with RDI, then jumped if not zero. So what's RDI? When we called it here? Zero? And is it not zero? No, because it is zero, it's not. So that should, so we will not jump, we'll move zero into a x and then return. So that's the return zero. So we jump back here. So the return instruction says pop whatever is on the stack into our IP and start executing there. Well, the call function put this memory address. So we start executing right from here, we move one into RDI. We then call func check lead. Now we test RDI with RDI. What's RDI here? One, we move, we check is it not zero? Yes, it is not zero because it is one. So then we'll go to lead, we'll move 1337 into a x and then return. So now we go back here and then we call exit and we're done. So now we've reused this bit of assembly code here to do what we want to do. And to do this, what I mentioned, Oh yeah, the test command is just like compare. So I think it technically has something else but in this context, it's just like compare. Yeah, so you know, check lead something we define and translate that and it's going to, you know, put it in assembly language, whereas exit might be more of a like a function from a library, you define, but it's still going to show up all the same. It's not going to make a distinction whether you wrote it or not, right? Correct. The difference is how exactly it calls exit here. So with a library function, there's more levels of indirection, because the library needs to be loaded and it needs to know exactly where it is. But yeah, if you start tracing this, you'll see like it usually call underscore exit is what your program will do. That calls into a stub that will load the library that's needed and then wire up where the location of exit is in that so that you go into the library, start executing it and you come all the way back. So if like, if I'm looking at this, and you know, let's say I'm peering into the assembly, watching it happen to scroll by, I might recognize the func check lead and like, okay, I get that. But will the func exit, will it look different then? Because it's a library call instead of No, it'll be exactly the same. I would. So it looks like just a set of instructions, you would know that it's a library function nine times out of 10, depending on how it's compiled. We'll definitely get there. So you'll be able to look at this stuff. Cool. Okay. So calling convention. So this is what I mentioned, that how do you pass arguments to a function? If you think about it, it's entirely arbitrary. x86, like 32 bit on Linux pushes arguments on the stack. Whereas in x86, Linux, oh yeah, here we go. That's right here. x86. So this is 32 bit Linux, push arguments on the stack, then call then the return values in EAX, whereas on x86, 64, which is AMD 64, the arguments are passed in the first argument is RDI, RSI, RDX, RCX, R8, R9, and the return value in RX. Again, I'll tell you that I definitely do not remember this. And I have to like, look it up every time. I think at a certain point you learn this stuff. And arm is different. And again, this is all Linux, because Windows is different. And other operating systems are different. Like, again, this is a convention. This just means that if I write a C code, and I want to call your C code, then you agree for talking Linux, AMD 64, calling convention, that I know how to get arguments and you know how to pass arguments to me, because if we can't decide on that, then we can't write functions that we can call. Again, another thing is, as we saw call function calls are not magic. A function call the call instruction literally just changes the instruction pointer to start executing at that function. So all the registers that were there were there. And so when you come back, you want those registers to still be there. So these registers are important RBX, RBP, R12, 13, 14, 15, these are callee saved. So that means that if you call a function that function is guaranteed not to mess with these registers. You'll see that you actually can you just have to always put them back when you're done. It's kind of like so. And other registers you can mess with, except for things like RSP the stack pointer, if you call a function, it better the stack may change, but it better be back right where you set it when you call that function, because otherwise you may be relying on the stack. We'll see this in practice. So, okay, let's use this knowledge. Let's do what was the challenge I was doing in office hours with somebody the one I was helping them? Who suggested one? I think it was 20? 21? No, I was 20 for sure. Computing averages. Okay. All right. Desktop, the dojo background gift wipes. I guess good for something. Oh, basic emacs. Yeah, emacs. Well, why you didn't send so emacs is great. You can use whatever makes you happy. I will not. I personally use emacs. Okay, first thing we're doing running the challenge. Lots of output. If you notice after last Wednesday, I added the compilation instructions here so you can the how to assemble how to get out the text file, how to send it and provide you with this command. So you can just copy this and run this. So you don't have to worry about that. Okay, we'll set some values dynamically in memory. We'll be working with control flow manipulation. Hey, that sounds very familiar because we just were talking about that in class. This involves using instructions both indirectly and directly control the special register RIP the instruction pointer. You will use instructions such as jump call compare and the like to implement requests, requests behavior, the request behavior. Okay. Okay. So in the previous level, which you've already completed, if you got to this level, hopefully, we computed the average of four integer quad words, how big is a quad word? 64 bits, how many bytes, eight, see, just, okay, which was a fixed amount of things to compute. But how do you work with sizes, you get when the program is running in most programming language of structure exists called the for loop. So this is a for loop for the sum of numbers from one to n. So we want to compute the average of n consecutive quad words where RDI is the memory address of the first quad word, and is the amount we need to loop for an RA x, what we're putting the return value in is the average. So this is saying that when we execute it, the start quad word is that this memory address, there's 65 of them. And we need to calculate the average. What's the so one thing I actually like to do is first copy this. Is that the correct comment statement? Is that the wrong syntax? Let's check. Yep. Oh, syntax, you're so fun. Okay, what I like to do is put the actual instructions what I need as a comment in the program so I can refer to them. Can you all read that in the back? Or do I need to figure out how to make that? Yeah, I got thumbs up. Cool. Okay, at least ran. So something happened. Oh, yeah, this was another. Yeah, this is all the output. Okay, but at least I can see that it works. Okay, but this is what we need to do. Please move the average. So what's the formula for the average? Yeah, so the sum of let's see all divided by n. So if you've reached this point up to this level, if you're not here, that's okay, we'll get here. If you reach this point, you've done division. You've actually done the average. So you know how to do the average. So let's take it step by step. So we need to so we know that our oops. So RDI will have the memory address. Our si has the amount we need to loop. x could be our average. So what so let's actually, I was going to write this in C. What would I write? So let's say I have like a character. Actually, this would be a, let's just call it an in pointer. And what would my for loop kind of look like? I need something to capture the sum, right? We said in our thing. So what would my loop look like, doing a lot of suggestions of what to type. Some plus equal words, I and then something. Sorry, this is not the right format for this. But some divided by n something like that. So this is another way to approach this right is to like first write either C code or even just pseudo code. Like what's what are you actually trying to accomplish? Then you can go into your assembly code and say, okay, great. So I will need things like an I, right? So let's move into what do we want? RBX, RBX will be our I. And then I would even write RBX is I, right to like remind me of what I want to do here. So move zero into RBX. I also need something to capture the sum. So RCX, what should my sum start out as zero? All right. And then what am I doing each time? So adding so I need to move into, let's say some register, I'll just say R12, RDI. So do I want to just get RDI? I'm setting the sum to be zero to sum is not a register. So I'll be RCX. Thank you. Excellent. This is why it's happy or super useful to do this from all these people. So RDI, but I want to offset this right. So what's my counter? RBX times what how what size are these? Eight, because they're quad words, right? The program is telling us these are eight, eight by values. So this will be the move. This is getting words into there. And then we want to add into some RCX, R12. And then what do we want to happen afterwards? Increment RBX, our I. And then how do we know when we're done? Compare RBX with our N, which is si. Thank you. Jump below. So if RBX is below RSI, which means we haven't yet gotten there. Then where do we go? Start loop? And where should start loop be? Here at the start? Oh, okay. And then what about if we're done? So then what do we need? So we've done all this now we need to do this part. So how do I do so I'll eventually need to do a division and is RSI. But I need to put the numerator in there, right? So div will am I using RDX? No, we'll technically use RDX colon RAX. So it'll use the concatenation of those, we can look up the thing for div, which you've done it earlier levels for sure. So move into RAX, the sum, which is RCX. Is that it? We just solve this? That's pretty good. Right on time. One minute late. I kind of wish it didn't work. You all are just too good. Alright, time for the stream.