 Hello everyone. This is a recording for CSE 545 spring 2016 and this is a lecture for the Wednesday class that I unfortunately will not be able to be there, but I know you're missing me just as much as I'm missing you, but due to the magic and beauty of technology we don't have to skip a beat in our class. So we're going to start right where we left off on Monday. So on Monday when we left off we were talking about how we had first begun our investigation into x86 and the x86 language. So first what we looked at was not the language itself but the actual x86 machine. So the important things there for the machine are what are the registers, how many registers does it have, what are the size of addresses in the system, how do we address memory, how are data values stored in memory with the end in this. And so now we continue our x86 tutorial and study and now we're going to look at the x86 assembly language. So here, so the x86 assembly language is how we write programs in x86. So the idea is well the machine, right, the x86 hardware, that actual CPU is executing ones and zeros. And as much as we humans would love to just use a keyboard with ones and zeros and just bang and pound sequences of ones and zeros into the computer, it's not really the best way to code a program. Even when we're talking about at this very low level here at the assembly language. So the idea is we provide and we're going to look at a slightly higher level language than machine code. So assembly language is basically right above machine code. And so we should be able to see, okay, yes, that the program actually is able to, that there is basically a one-to-one mapping between this assembly language and the actual ones and zeros that get output. So an x86 assembly language program is made up of directives. So remember from our previous study, we've seen that on GNU and Linux, the AS command, the assembler command takes in x86 assembly language and then outputs that binary object, which is that actual machine language. So just like when we program in C and we have directives like the pound symbol that our preprocessor macros for the preprocessor, those actually aren't part of the language, but they're important parts and information essentially to the compiler. So here we're actually talking about assembly language is a specific compiler and the assembler takes in assembly language and outputs machine code. So we can have directives that are commands for the assembler. And in those directives, we specify different sections. So dot data is how we specify variables in x86. Then we have instructions to the actual operations. So they'll look something like this, JMP to some address. So this JMP is an x86 mnemonic and it has a very precise execution semantics. And it also maps to an explicit binary encoding that you can actually look up. So unfortunately, so for any language, right, we have to define the syntax and the semantics for historical reasons that I'm not really going to get into. There's actually two possible types of syntaxes in x86. You might say, well, Adam, how is that possible? How can we have one language with two possible syntaxes? That seems crazy. Why would you want to do that? We don't in our normal high level programming languages, we don't have multiple types of syntaxes for C for instance. And it turns out it does. So in the assembly instructions, as we'll see are very kind of simple. But a lot of what we're going to be doing, right, we've seen the CPU, we've seen we have registers. So a lot of what we want to do is do either computation or move data either from one register to another or from memory to registers or from registers back to memory. So the key difference between the syntaxes is what is the order that that happens in in the actual assembly language instruction like this jump instruction here. So there's two types of syntax or AT&T syntax, which have the mnemonic as we saw here like jump, and then first the source then the destination. So it would be, for instance, move EAX into EBX is how you would read that. And that's used by ObjectDump and the GNU assembler AS. The other style, DOS or Intel syntax, which is used by the Microsoft assembler, NASM and IDA Pro, the mnemonic remains the same, right, which makes sense because we're using mnemonics from the x86 assembly language. But the destination and the source are flipped. So in this, you read move the source into the destination, but you have to know move source into destination here. So it's important, the real important thing from this is to know that there are two syntaxes. So if you're trying to read x86 assembly code, right, assembly language, you actually, in order to understand the semantics of what does this instruction mean, you have to know precisely what syntax you're using. So if you don't know that, you're never going to be able to understand the code. So A, it's important that you know that there are two different types of syntaxes. And in a lot of tools, like for instance in GDB, we can set the syntax specifically to either Intel or AT&T. For this class, we're going to use AT&T syntax from here on out. I hope. So hopefully all the examples, if I like that, if there's any questions about it, you can feel free to either email me or ask in class. So in our data section, we can define data. So just like in a normal programming language, we may want to define constants, right? And in the Intel syntax, hexadecimal numbers start with 0x. And we can define any number, actually a good number of data objects. So we can, then the basic syntax is a label. And this label is actually not specific to the language itself. But it allows us, or it's not specific to x86 itself, the CPU knows nothing about labels or how to handle them. But it allows us in our program to reference this label and know that it's that segment. And the assembler's job is to then link up those references to this actual address. So types can be just as we've seen previously when we were studying the x86 machine, types can be either bytes, words, double words, or quad words. And so for instance, in a dot data segment, you can define something called my variable. And it can be two double words. The first one is 12345678. And the next one is 23456789. We can also define a variable bar and have it be just a word. And so that would be the 16 bits here, we can define strings. So this my string is a string that's three. So it's bytes. So you can see here from the DB, it's a bytes. So there's the bytes f, the whatever x f is an ASCII. If we were in class, I'd ask you to look it up. f00, all in ASCII. So those ASCII values followed by a null terminated zero. So now that we're able to define, so this is how in x86 assembly language, we can define constants and data. We need to know, okay, how do we access memory, right? So before we actually looked at the memory mode of the processor, right? So we looked at the differences between the flat memory model and the segmented memory model. And in x86 to access memory, regardless of kind of the mode of the processor and the mode of the memory accesses, all memory access is going to be done with five things here, the width, the base, the index, the scale and the displacement. So the width is fairly simple. The width is the data size that you're transferring. The base is the starting address of the reference. And so the index, so the index is an offset from the base address. And the scale tells you what the multiplier is of the index. And the displacement gives you a constant offset from the base. And where the width, you can tell the width based on the address suffix of the description. For instance, B will be byte, S will be short, W will be word, L will be long, Q will be quad. So the basic idea is when calculating an address in x86 assembly language, the actual address that you're going to be fetched or that we're either going to store or load data from is going to be the base plus the index times the scale plus some displacement from that. And this is going to be represented into the AT&T syntax for x86 assembly language like this. So whenever we're going to use a memory reference, we're going to see this displacement and then we're going to have the base, the index and the scale. So for an example, we could see an instruction like this. So let's decode and break this instruction down. So here we have a move L and you've got to make sure that that's not a one. So we see this L, we know from this it's a long, we know from our discussion of data sizes that the L is 32 bits. So we're moving 32 bits, we're moving a word 32 bits. So then the next question to ask yourself is okay, then what are the semantics of this part of the command of the assembly language? This is x86 assembly language instruction. So remember with the AT&T syntax that we're talking about, the destination is always the rightmost. So here we're moving whatever this memory address is into edx. So that after this instruction is executed, the register edx holds the value of the word that is at this address. So then how do we calculate that address? Well, we just simply follow this formula. So we take the base is eax, we're going to take eax. So whatever value is currently in eax, we take that value, we add it to ecx times four. And then whatever that is, that's going to give us some value and then we'll subtract hex 20 from there. So okay, let's see if we can try to develop some sort of intuition about how we can think about the base index scale and displacement. So I like to think about these first three, the base index and scale, I like to think about them like an array. So if you think about a C array, a C array is just a contiguous sequence of bytes or some kind of data type. So if we have a byte array, which is a character array, a char array, the address would be that address of our array, so it would be eax. And then the index would be our index into that array. So it would either be zero, one, two, three, four, just like we want to access of what element of that array we want to access. Then the scale gives you the size of the data type of this array. So in the case of a bytes, the size would be one. So we know that the first byte is located at, or the zero-width byte is located at eax. So it'd be eax plus zero times one. And the next byte is going to be eax plus one times one. So it'll be the next byte, eax plus one. And so for something like four, four would mean that the size of the data that's in eax is that eax points to is going to be four bytes long. So it's going to be a long. So this means that ecx is essentially our index into this array. That's in eax. So wherever eax points to, we're going to go down four times ecx, which is going to give us that value. And then this displacement allows us, it actually becomes very important when we're doing local variables as we'll see later. So kind of, you know, and obviously the order of this addition doesn't matter. So I like to think about it as eax minus 20 hex plus ecx times four. We're going to move that, what's in that memory location into edx. So another important thing to note here, right, is when we are addressing memory like this, we're actually doing essentially a dereference, right? So what's in eax is an address, but we're moving the value in that address, well, in that address plus ecx times four minus 20, wherever that address is, we're moving the value in memory into edx. Okay, so some more examples. So the way you can get the most out of this, if you're, well, you have to be watching this video from somewhere else. Otherwise, you're in this hotel room with me, which would be very weird. So for addressing memory, so what would be most helpful is to look at this and kind of pause it and go, okay, what do I think this output of this command does? What semantically does this x86 instruction do? So we'll walk through this one. Okay, it's a long, so we're moving 32 bits along. We're going to move ebp minus 8 into eax, right? So this is copying the contents of the memory pointed by ebp minus 8 into eax. Let's look at something slightly more complicated. So what would this assembly instruction do? So here we're moving, so remember, the first thing the size, the L tells us 32 bits. Destination to source, so source is on the right. So here we are, so here we have an example where we have an index, the index is eax. Sorry, we have a base, the base is the register eax. We don't have an index, we don't have a scale, and we don't have a displacement. So we are copying the contents of the memory pointed by eax into eax, right? So essentially we're dereferencing eax and storing it back into itself. So one of the things you can do is try to convince yourself why this is a memory dereference, so why this is similar to a pointer dereference. Okay, so now let's look at this example. So what is this example doing? Right, so here we're moving contents from memory into a register, and so now here we're moving along once again 32, we're moving a register, the value that's in a register, into memory. So which memory address? We're going to take edx, we're going to move it into edx plus ecx times 2, right? And so whatever's inside that register eax is going to be moved to that memory address. Okay, so one thing we haven't seen before, so in the syntax that we're using for x86 assembly, the AT&T syntax, constants are represented with a dollar sign. So here we're moving the constant 804a0e4 into the register ebx. So this is how we can copy values. So important thing to note, the difference between this and all the other examples, all the other examples had memory access, right, which is essentially doing pointer dereferencing. But here we're just copying this value into ebx. So if we wanted to do something different here, here this would be the way we could actually have a constant memory access memory dereference. So here we are taking the content of the memory at address 804a0e4, taking the value that is at that memory location and moving it into eax. So this is something that's going to be important and is going to definitely help when we're looking at x86 assembly code. So I highly recommend you take the time to walk through these examples to understand exactly what's doing, what's happening. Okay, so instruction classes. So the instructions, the types of x86 assembly instructions fall, there are all kinds of different classes. As we've seen, some allow us to transfer data, right? So if you think about, these are actually some of the fundamental things that we want processors and CPUs to be able to do. So we want to be able to move data from memory into a register. We may want to exchange data between registers. We may want to, we'll see, pushing and popping. So these actually implicitly use the stack pointer. So these push, push will push a value from a register on or memory, I believe, on to the stack and pop will take that first value off the stack and put it into a register. We can also, so these are kind of the getting data into our program. We can also perform computation on that data. So we have all kinds of binary arithmetic where we can add things, subtract things, multiply, divide, increment things, decrement things. Basically all the building blocks you need to write and do any kind of complicated logic. You know, important things to think about and to note, what happens when we have an overflow? So an overflow would be when we perform one of these arithmetic operations, addition, multiplication, and we have a value that's more than 32 bits, because there's a hard limit on the size of numbers we can represent with 32 bits. So if we have a number that's greater than that, what happens? So I'm not going to go into the details of that here. We also have some logical comparison operators. So we have ands, ors, x, ors, nots. So you can build any kind of complicated logical expressions that you want. And then finally, with those building blocks, we want to be able to change the control flow of the program depending on the conditions of these logical checks. So we need some kind of control structures. So we have ways to jump to different parts of the code. We have x86 instructions to call code. So this helps us implement functions. We can return from calls. We can do interrupts. So an interrupt is when an interrupt occurs, it basically forces the processor to immediately jump to an interrupt handler. So some other piece of code will handle that interrupt. This is specifically how the user space application can talk to the kernel by issuing interrupts. iRet is an instruction to return from the interrupt. And so in order to do interesting control flow, we want to actually jump to and execute certain parts of code only conditionally. So the cmp x86 instruction allows us to do this. So if we compare source and destination, it subtracts the source from the destination and doesn't save the result anywhere. And this is actually what this cmp instruction is, sets all the bits of eflag, the eflag registers. And then we have different types of jump instruction that will jump if certain eflag bits are set. So jne, which is jump if not equal, will jump only if the zero flag is zero, jump if equal, the opposite of that will jump to the code if the zero flag is one, jump greater than or equal. It checks this. Oh, je, jump if I believe an overflow occurred. No, maybe that's wrong. I have to look up exactly what these ones were. But the idea is for all these ebit flags, we can jump depending on the conditions of there so that the programmer has control over what happens in these circumstances. So this control, if the destination can be a constant, so it can be, hey, jump to this memory location if this thing happens, or it can be indirect. So indirect would mean that, hey, jump to the address stored in register eax. Right, so this is something that can actually change at runtime where you actually jump. And there's various other instructions, how to deal with input output devices, other kinds of instructions that are going to be interesting is a no op. So nop stands for no operation, so basically telling the CPU to do nothing. So maybe it's interesting to think about, well, why would you include an instruction that specifically does nothing? So if you remember back to your computer architecture days, so CPUs have a pipeline of instructions and they're executing actually multiple instructions concurrently in this pipeline so that there's a lot of instructions that are going on through that pipeline. Depending on the actual CPU, you may need to specify no ops in order to handle this, the concurrency and the pipelining here. So no op instructions allow you to do that. That isn't to say that this is the only type of operation that is a no op, you could have an instruction that, let's say, moves a register to itself. So move eax into eax. You could do that instruction, it doesn't change anything. Okay, so a key part of x86 programs is how do we actually get something to happen? So you can go through all these instructions, you can write these system, you can write these x86 instructions, but how do we actually get something to appear on the screen? And the way this happens is through system calls. So system calls, the whole idea of the system call is this is what defines the boundary between the application and the operating system. So the operating system defines a number of system calls and the applications invoke those system calls to call parts of the kernel. So this is part of the abstraction that the kernel provides. So instead of every application having to understand how to deal with ext2 file systems, the operating system has a system call that says open a file and write to a file. And in this way, applications don't have to have that functionality of how to deal with all types of different file systems. The operating system needs to know how to do it and the operating system provides this standard interface that all applications can call and invoke. So this system call interface explicitly defines here this interaction between these these two processes. So usually, you know, when you're code in C, even though we talked about, okay, you code a program in C, the C program gets compiled by GCC into assembly, which gets compiled by AS into a binary file and that gets linked with LD into an actual ELF executable. So and you say, well, Adam, I can open files and I can print to files and write to files. And I use stdio.h to do that, right? It handles that. So I can use fprintf to print to files. I can use printf to write to the console. So usually, these system calls are invoked through libraries. So actually under the hood, if you dig into libc, the printf function does nothing but calling the write. So Linux and x86 has a write system call to write out a string to the console. And so all printf is doing is parsing the arguments that you're passing into printf and calling that system call write number of times. So on Linux x86, the way an application calls into the kernel is through an interrupt. So the specific interrupt it says is int 80. So int 80 tells the interrupt handler, hey, the program that called this is trying to call a system call into the kernel. And so when it does this, right, so essentially, if you think about it, this is really kind of like a function call. We're trying to call some function inside the kernel to do something for us. And so when we see this, but to do that, we have to have a well defined interface so that we're not in, if you think about the process space, we're our own process executing our own thing. We don't just jump into kernel memory and start executing kernel code. There's a lot of protection mechanisms that need to go on. So there's a standard that defines on Linux, how do I tell the OS exactly what system call I want. So the way you do this is EAX contains the system call number, and then EBX will contain the first argument, ECX will contain the second argument, EDX will contain the third argument. And I believe any other arguments are put on the stack. And so we can look at a hello world assembly file. So we can say that we have some data, and we'll give it a label called hw for hello world. And our data is a string. And so we have the string hello world slash n, then dot text specifies, okay, this is a compiler directive that says, okay, the next thing is code dot global main. So we're going to define a label called main. So the colon here means that this is a label. And dot global says this is actually for the linker. So this says, hey, when you look at this, I'm defining an exportable label called main so that other people can link to and call this symbol main. And this is important because this is how the loader knows which method we want to execute first, right, where the entry point to our function is. And so here we know, so this directive, this dot global tells the link linker, hey, I want to execute main as my entry point. So the simple hello world is first going to move the constant four into EAX. It's going to move one into EBX. And then it's going to move HW. So HW is this label here. So this is a placeholder, right, this HW is a placeholder. And so we're going to move, which will be at link time, this will be the address of wherever this string is located in memory. So we're going to move that string address into ECX. Then we're going to move 12 into EDX. Finally, we're going to call into AD, hex AD. So what's going to happen? Okay, so we do this. Well, we know from what we just talked about that the system call number is going to be put into EAX. So EAX is going to be four. So four is actually the read system call. So now if I can, all right, if I can go into my terminal, I can say man, let's, I think it's section three. No, I think it's section five, six. It's not a bash built in, two. Okay, perfect. Okay, so in UN universal standard.h, this defines all of the system call parameters here. So this is actually the function that I'm calling. So if we look in here, let's do that. Okay, I didn't plan on doing this. So we'll, maybe I completely mess everything up. User lib, user lib, find user lib. That did not work. Maybe I don't have the header files installed. What happens when you do a live demo that you didn't plan on doing beforehand? So we'll see. Ah, user include. That's right. Okay. And we can see it's not only in user include. So we have user include UNi, std.h, but we also have it in the source. It's getting into now Android stuff, which we don't want. We can see here that it is also in the kernel source code. So in this distro that I'm using, it includes the kernel source code. So let's look at this file. And so we can see this is a .h file. It's got a lot of defines here. Okay, and we can see that it defines this read command here, this read function. And I thought it had the list of all of the, maybe it's in sys, assemb, maybe in ASM, assembly. Ah, there we go. Okay. So we can see that in this one .h. So these pound of fines says if it's a 32 bit architecture include UNi, std, underscore 32.h. So now I can look at 32.h. Bingo. Here we go. So this file contains all the system call numbers. So we can see that, ah, very good. I've been talking about the wrong thing. It's very good that I did this. I'm actually not trying to read. I'm trying to write. So I can see in here that by moving four into EAX, I'm saying I want to call system call four. So this shows you all of the system calls that the Linux library is actually exporting to you. There's exit, fork, read, write, open, close, wait, PID, create, link, unlink, time. You may have actually used some of these functions and maybe not realize that you weren't actually using a libc function. You were actually using a kernel system call. So the Linux kernel was actually doing this on your behalf. And we can see it goes on for a long time. I wonder where the bottom is. Let's see. All the way at 348, process, vm, write, and v. Okay. So now that we did all this, we know that we're actually not calling read. We're calling write. So we see, okay, write takes in a file descriptor, a pointer, a void star. So void star is a pointer. So the address of a buffer and some count. So what does it do? It says it writes up to count bytes from the buffer pointed to by buff to the file referred to by the file descriptor FD. The number of bytes written may be less than count if it's writing to a physical byte. So it's writing the number of bytes from the buffer to the file descriptor. So then let's sync this up to what we know about system calls from this code here. So we know that four into EAX defines the write call. So write means, okay, perfect. I know, I know that we're calling the system call that we're trying to call is the function write. Now, the first, so what we know about system calls is that the first parameter of the system call is going to be stored into EBX. So if I look here, I see that one is going to be stored into EBX. So one in write, so one is going to be then the file descriptor. And so the other convention about UNIX is, let's see, standard out. Yeah, okay, perfect. So important thing about UNIX is that on program startup, the integer file descriptors associated with the streams standard in standard out and standard error are zero, one and two. So what this means is every process that's executed on UNIX, file descriptor zero, the integer zero maps to the stream standard in. So every time your program is reading input from standard in with either gets or scan F or if you're using one of the C plus plus libraries, this is reading from the stream standard in. And so here with writing to file descriptor one, we're actually writing to standard out. And if we wanted to print out an error message, we would write out to two, which is standard error. Okay. So now if we go back to the write call, now we know, okay, we're writing to standard out. And what are we writing the standard out? Well, the next argument here is the buffer. So if we look at here, we're moving hw, which is this address of the string into ECX. So that's what we're printing out. So ECX is going to contain the address of the string hello world. And then we're moving 12 into edx. So 12 is the count. So this is the number of bytes that's going to be output, a number of characters. So if we look here, we see 123456 for the space seven, eight, nine, 10, 11, 12. So this should print out this whole thing. And then we call in 80, which means we invoke that call. So let's maybe see if we can write this. Yeah, let's try to write this. Okay. Test.s. So we're going to give it the .s file name so that GCC knows that this is a, oh, and I already have a test.s in there. 545.c. Right. So this is not something I was planning on doing, but I think it'll be fun so that you can see. And then we can actually talk about some of the things here. All right. So we're moving four into eax. This tells us that we want to do the right system call. We're moving one into ebx to say we want to write two standard out. We're moving the address of wherever the linker decides to put hw to the hello world string into ecx. We're then going to move the constant value 12 into edx. Then we're going to call int 80. And then because we're good people, we are going to return successfully from this program. So if I write this out, assuming I've done everything correctly, this should work. If it doesn't, well, it'll be fun. Oh, I would be because I named it hello world.c, which is not a c file. Hello world dot s. So I'm going to compile it. Remember, I'm going to use the m32 flag to say I'm compiling a 32 bit program. And I'm going to go hello world. It compiles. If I execute it, bang. So everything works correctly. We can see that it actually is outputting hello world. And I think I believe I give this the dash s flag. It will output. What does it actually do? Okay, well, I already have the assembly file. Oh, this is what I wanted to do. Okay, so I'm going to compile this as 32 bit. We didn't show exactly how to do this, but we will in a bit. So I'm going to look at the, what I want to look at here is I want to look at this assembly code that the, that AS actually generated for this hello world. So if I look at this, at this, I have to do this on the object file, object dump is going to try to turn the machine language, the zeros and ones of this a dot out file, and it's going to try to output that to me and show it to me. So I want to run this through the less command. And I want to see the main function. And I know that main is that function that I actually wrote. So we can look here and we can compare it one to one. So you can see, okay, move four, move four into EAX, move one into EBX, move this 804960C to ECX, move hex C into EDX. So C is 12, then call in 80, then move zero into there. So what is this address? Let's look up this address. So we'll search for 960C. So what this output is, the way to interpret this output is object dump has read the elf file format here. And it knows the mapping that the elf file format says the mapping between that file and where those things should go in memory. So in one of those elf headers, it says, hey, at 0804960C, there's a label called hardware. Now, this on the right here is object dump trying to interpret these bytes as if they were x86 instructions. So this is going to be garbage. And as we can see, it is in fact garbage. But in this middle column here, we can see the bytes. So we have the byte 40. So at this memory address, we have byte 48656C6C6F2576F7260C640A00. And if we look this up in our ASCII table, so if I look up what's hex, I want to know what is ASCII in hex 48. So hex 48 is right here. And it is the H character, which matches up to the H here. And we look at 65. I bet you that's going to be E. Actually, I know, so sad I have not memorized the ASCII table not quite yet. If I look at E, E is 65. So I know that this string is this at memory location 0804960C is the string hello world. And remember, the string is a sequence of bytes followed by 0. And so the linker actually took this program and said, OK, I'm going to put this string at this memory location. It just decided. And then so here in this instruction, as we saw in Main, this instruction here, so then it changed this instruction and actually put in this exact address in here. Cool. OK. So now we've actually looked at this. I actually encourage you to do the same thing. It'll help you become more familiar with assembly language and the compilation process. And I think it'll be hopefully an interesting and educational experience. OK. Let's go. We've already done that. So there's a couple other segments of X80s of ELF files that are important. So the PLT and the global offset table, the GOT. So this is actually getting into more details about how this dynamic linking is done. So we've actually just seen a little small example about how linking is done on when you have static constant values when you're doing statically linking. So we use the name HW, but that name HW needed to be matched up with an actual address. So the linker is the one that did that. So when we're doing static linking, it's very easy. The linker does all this, changes all the labels to addresses, and outputs everything. But the question is, how does this happen at runtime when we want to do dynamic linking? So we essentially have to do this mapping. So the idea is when your program calls a shared library function, the address of what's called is stored in the Procedure Linking table. And the address is going to contain an indirect jump to an address that's in the global offset table. So the idea is basically the way this works is the first time you call a function like printf, the global offset table. So the global offset table is a table that at runtime tells you the location of all of these dynamically imported libraries. So the first time you call a function that is dynamically imported, in the global offset table is an address that jumps back to the PLT because there's no way to you don't actually have that function stored in your program yet. So basically the linker actually will do its magic at runtime, find the proper version of the .so file, load it up, read the memory, get the function, and then it will change the GOT entry so that the next time that printf function is called it's going to execute it directly. And we'll see this is a little future note that the PLT is actually read only but the global offset table is not because clearly the PLT has to overwrite the global offset table. So great. So we will end this here, this lecture here. So I hope this has given you a good overview of x86 assembly language. I encourage you to play around with this, do these types of things on your own, and when we get back we're going to go into how processes are actually loaded and executed and then we'll actually start really getting into some attacks. So I look forward to seeing you all on Friday.