 All right, let's get started. So I'm still not Adam DuPay. I've been trying for an entire two days. No such luck. I've come into application security. There's a topic much near and dear to my heart, even though networking is very much immediately applicable and so forth. Any questions about networking from Gua? Anyone who realized I hate? And it's Tuesday, I forgot to remind the others. Did we, there's no more, do we need to get up? Really? All right. So we're going to dive into application security today. I have a couple of places for my 466 class. Who cares before? Just a few people. So this might be kind of a review with a horrific twist as we get to it in a moment. But if anyone else? It's application security. In that, the security of networks is super important. The security of whatever other stuff you learn in the course is super important. Crypto and so on. But in that, we use computers for applications that are running on them. Number one, I mean, I'm sure people do, but it's kind of rare to just have a computer and be like, oh, that's cool. Or then not use this for anything. Applications run on these computers, either for local use or for remote use. Local use might have a word crossing app, VAM, whatever, people write essays in these days. Remotely, you have, of course, Apache, MySQL, et cetera, all these network services that kind of power the modern web, the modern internet, and so on. So these applications are very important. And who wrote these applications, rather, might have posted dreams for the behavior of the application is not determined by hopes, dreams, and design. It's determined by the specific code being executed. So if you're going to dive into what that code and being executed is, and what the environment of it is, and so on. And probably, we won't get to it today, but next class, we will, I don't know what to do with you or whatnot, various attacks that can be mounted on an application to violate the sort of contractuality and integrity of the availability properties. You've all gone through the CIA triangle. In fact, I first saw the CIA triangle when I arrived at ASU as a professor. But it's kind of, I guess, a reasonable abstraction way of looking at it. So let's look at how applications run. An application doesn't run in a vacuum. An application runs in a machine that is configured that is on a network that is running a specific operating system and a specific file system. And this file system is used by the application for resources, libraries, et cetera. And the kind of application file together with this environment put it into memory in an operating system to create a process. And that's typically what you interact with as a user when you're running an application. And then application mobility analysis is the idea to take an application. And you look at it very closely and you check if it has a vulnerability that you could exploit. And there's a number of vulnerability sources, places where vulnerabilities can be introduced. One is vulnerabilities can be introduced in the original design program. So I said what actually matters, the actions that are actually executed, I've done so by code, running on your computer. Well, you don't start an application's life in code. You don't think, oh, I'm going to write an application that is going to do int main, open bracket, follow that, right? I'm going to write an application that is going to load a file from a hard drive and display it as a CAD gift or something. So in that design process, that first step, oh, I'm going to create this application, you might introduce vulnerabilities. And we say, I'm going to create an application that will display CAD gifts. And you know it would be cool if on the bottom screen to display my password. That's like a design model vulnerability. You've got an application-level vulnerability, right? You're implementing the program. You've created a program that can load CAD gifts. You don't intend for it to be able to display your password. But it turns out that the way you wrote it, anyone using it could open any file, not just CAD gifts, and so they open your password file. And then you can have deployment vulnerabilities, where you think of that, like, OK, while doing implementation, you can pass that bug. You prevent arbitrary files from the system from being open. But by accident, as you're deploying the application, you install it together with your password file in the CAD gifts folder. And on attack, you can display your CAD gift file. So vulnerabilities kind of happen at all stages of an application lifecycle. Vulnerability can be exploited in a number of different ways. But there are two kind of major attack classes. One class is a local attack. This is an extremely powerful attacker that is on your machine. This is realistic often, right? You might connect somewhere to submit your home, for example, in this course, or another course that you've seen through your SSH into a server, and you submit your home. When your SSH is in that server, you are a local attacker. This gives you some power, such as the ability to directly interact with an application through a lot of interaction channels. If you standard in with standard output, you might be able to standard what are called signals, operating system messages, create files that you might have access to before running an application to affect the behavior, and so forth. There are certain vulnerabilities that might allow us to, for example, global privilege escalation, where because of the intricacies of how the system is set up or the application works, you can run the application with more privileges than it expects you to have. You can do other things. You can heavily craft the environment before running the application, right? So the application expects there to be an environment variable pointing to the folder of caddis, and you can change that before running it. That could be an attack vector and only a local attacker can exploit. In general, these attacks are the more powerful class of attacks, but it's sometimes less impactful. You know, if I'm a web server that has a local vulnerability, I don't really have to worry as much, because the web server on Google, the web server on the google.com, none of you have SHI with Google.com. If you do, I have other things to worry about, right? But if you don't have any local laws in application or any laws in application that are only exploitable by a local attacker, aren't that big a deal. On the other hand, a remote attack is an attack that can be launched from the outside. On Google.com, I have a web server running. If someone can connect to that web server remotely and execute an attack, I have a very big problem. But these generally are more from the stream. The web server is already running. You can't modify the file system and so forth before running a web server or rather people connecting to a web server, because you don't have access to it. So in that sense, the remote attack, though it is, much more directly applicable, is harder to perform. That being said, remote attacks, I don't know if any of you were cognizant, cyber-citizens, back in the year 2004 or something. But there was a period of maybe eight years from very early 2000 and 2001, 2002 to 2008, 2007, where every couple of weeks or every month there would be a massive warm-up. And the internet would become like a battleground. We weren't the people trying to stop the warm. I had an undergrad at a work study as a university health desk. And one day, I had a really crappy sleep getting built almost as bad as it seems to be right now. One day, I was up at like 5, 6 AM reading slash dot, which was the reddit of its time. And I saw it told us that, hey, there's a new massive worm attack going on. I don't know what exactly it was, which worm. But I was reading all about it. I already expected, you know, 380,000 old-sund internet and it's spreading like crazy and blah, blah, blah. Like, oh, OK, I think I'm going to be sick tomorrow for work. I'll be sick with someone else. I feel bad about that, too, this day. But these effects were constant. Every month, you have a massive warm-up rate driven by the capabilities of essentially remote attacks against different parts of internet infrastructure. And interestingly, even though these attacks are harder to pull off, they're also harder to stop, right? So these massive worm attacks that were ongoing, for example, in 2002, let's say, don't pull me on the year and the worm combination. In 2002, there was a massive attack that impacted a ton of SQL databases around the world. And there was also a worm. I think it was this. It said worms called SQL Slammer. And Slammer impacted a ton. And I think that this was the attack. I think in 2002, SQL Slammer impacted millions of open databases on the internet. And the life cycle would be to perform a remote attack, to start scanning the internet, and attacking anything that got from a new victim. SQL Slammer is still sending out messages. So 17 years later, SQL Slammer is still out there, right? So these remote attacks are very, very hard to stop. It's much easier to purge local attackers from your machine than to purge remote attackers from the entire internet. So let's move on to how vulnerabilities might be introduced in such a way that these attacks can exist, right? Typically, nowadays, we write applications in a high-level language. This was in the case back in 1999 or whatnot, but well, it was the case in 1999. Probably in the case of, like, 1989. But nowadays, we write applications in at least a language that is C-level or higher, right? You don't typically write assembly to create a web server. That application is translated into an executable. It's called either a compilation, or just a type of compilation, or whatnot process. We'll talk about interpretation in a second. It's loaded to memory, executed, runs, and eventually terminates. It's very straightforward. I mentioned we'll talk about interpretation. Of course, it's easy to see how you write a program in C, and it's compiled into an executable. And then you run it, and we'll actually talk about that in depth as well. But let's talk about interpretation first. So Udia has used a scripting language. Awesome. I highly recommend it. It's very fun. Let's just launch a scripting language. There's an interpreter. I byte in. It's an interactive Python interpreter. I can do cool stuff, just pulling stuff out of my hat. I can print every number from zero to 10, like a one-year-old or a nine. So that's pretty cool. In C, that would take tons of compilation and so forth. The interesting thing is, when you saw just there, I did a import blah, blah, blah, blah. And I typed that in by texting it executed. You would think, OK, 500 interpreted language. It takes every statement I write, interprets it, and executes it. That is actually not the case. It first compiles it to an intermediate representation. Let me show you what this looks like. It's called Python bytecode. And then that is what's actually interpreted. I'm going to create a function that's going to do what we did. So here is my function that can print out zero to 10. This is the bytecode. This is a function compiled into Python bytecode. And there are tools you can use to uncompile it. There are tools you can use to view this bytecode. I didn't actually expect to go down to standard, so I don't have the tools ready or I don't know. I'd have to Google it. But this is the bytecode, and you can kind of see it. So we did a zero through 10 range. What if we do a zero through 65? You'll be able to see that change here. Oh, no, no, no. Because it has. No. Does anyone see a capital A there anywhere? No. Anyways, the point is, this is the bytecode. How do we code that? That you can, of course, change it to do a print twice. Nope. Here's the code. You can see it's longer now. Everything until here, it's roughly the same, right? Up until gh. And if you go onto here, there's another gh. So this looks like whatever this is, is the print byte null byte null byte gh. And then q seems to be the end of the for loop, because there's also q there after the last print. And actually, we can do a wrapped loop here. And now you can print it out again. And probably somewhere, there'll be two q's. So here we have gh, q, and then there's another q. So q something null w seems to be the break end of the for loop. Maybe. I don't know. We're just reading basically binary crap on my terminal. So the point is, the Python that you write and the JavaScript that you write, I don't know about the bash that you write, just not the command line. Typically, nowadays, for a first kind of serious language, it gets converted into a byte code reputation than executed. Your text isn't being executed directly. The other thing that these languages can do is create stop dynamically. So I can actually create text. I can say x equals print. And I can say y equals hello. And I can say eval x plus y. Oh, I can't. I can say exact x plus y. And it actually executed. There's some dynamic, some level of dynamic code building that can happen because in the language itself, it has the capabilities to compile the code, generate that byte code, and execute it in the language runtime. You're writing an application in seed, which we'll get to next. You don't have this capability. So let's talk about seed. Who here has written in seed before? Was it the cards for this course? Is it? No? Well, seed is, of course, kind of the granddaddy of compiled languages. It wasn't the first one, but it was the first one that kind of led the world on fire, let's say. C is a language that is a little obtuse. There's reasons for things being in seed or where they are that are a little confusing to someone jumping in. But let's just show an example of C code. So here's hello world in seed. Now let's just be nice here and include cdio.h. This is a library that provides this code's functionality. And then, hopefully, you've all seen this. You compile something, and you run it. Did anyone notice that when I typed ex, I automatically finished it with x. All right, example. And then it says hello world. C is a language that is non-interpreted. You write it. You compile it. And then that produces an executable that you then run. This has a couple of side effects. So for one, all I need to do, all I need in order to run this example now is the example and some libraries associated with it. The example is not very big. It's 8 kilobytes that is produced. We can strip it down to be much smaller. Whereas for Python and the library is associated with it, there are a couple of facts. An entire Python runtime is 10 to the megabyte. So much smaller. It is also much faster and much more control. When you write C, that is more or less what is going to be executed on your system. You don't have to worry about Python doing weird things and so forth. Let's talk about this compilation process. I just did GCC example that produces from my C source code an executable file that I can launch directly. This skipped a couple of steps. Rather, it did it for me. The first step in compiling an executable is, and you can run it with this s step. Actually, the first step. So we have this example. Well, that's the binary. We have this example.c. We include sdio.h. That is some syntactic sugar C gives us that can be resolved by the C preprocessor. This is the first step. The C preprocessor takes the included file and paste it into my source code. So this is some crazy stuff that all together adds up to my ability to print hello world. And then at that, here's my little hello world. So all righty, things are getting a little complicated. Then we do the compilation step, where my code is translated into a lower level language. So it's similar to Python where you translate the byte code. In compilation, my code is translated to assembly. So now you can take this step two, typically .s. This is now assembly code. So you can see it and recognize something, recognize hello world. But and actually, it is once again minified in some sense. A lot of the junk that was in that sdio.h include is gone. And we see just code. This is the function. This is super, super, super close to the actual instructions running on the machine on the compiler. Or the next step is called assembling it from assembly to byte code. When we assemble it, essentially what happens is all of these are converted directly to one and zeros. So push qrvp is converted to eight bytes directly. So this is kind of a direct mapping of what will end up running on your CPU, which is super cool. And you see it's set something up. It moves into what those are. And then it calls puts. And that's really super intuitive, right, everyone? Of course, it calls puts. And that's when you've heard of you can return 42 here instead of just exiting. So then let's see what that looks like in our code. So we first do the preprocessing step. Then we do the compilation step into assembly. And if we look here, we see that here's our 42. And that's going to be what's returned in then. Pretty cool stuff. All right. All right. Then the next step is we went from source code to preprocessed and then we have preprocessed source code. Then we did the compilation step. Usually when people say compile a program, they mean directly to a commutable form. From filing, it actually does this step. What probably we use is it actually does this step of converting it into that assembly. From there, we use an assembler to create the binary file. Let me show you what this looks like. We just run AS. Example step 3. And we output example step 4.0. OK. Now this is what's called an object file. An object file has no longer text that we can read. We can read this just fine. That's VIM. This would happen when I try to open this object file. So you can still see how the world in there. See some metadata. We'll go to 7.4.0-1. So this 7.4.0 is my DCC version. So it puts some metadata. But the rest of it is kind of insane. It is a binary format now, not for human consumption. It contains the code that was directly translated from my assembly into binary. It contains some metadata that will be used to create the final executable. For where this isn't yet the final executable. It contains information about symbols. So if I have various variables, it'll have information about them. And debug information, if I had included it during the compilation step. So to all of these steps, I can add a dash D. If you ever debug your YDC code part of the dash D, if you debug symbols, that's what happens there. And then the final step is the linker. And the linker combines the object file with other object files. In this case, you only have one object file, so that's easy. Resolves a couple of inter-object dependencies. So if I had one .c file that I called function and another .c file that I combined with another file, then I need the linker to figure out where the function that is being called by one is another. That it'll do that resolution. It will also optionally include all of the libraries that you need statically inside your file. Otherwise, it'll leave them as external references. Say, hey, when I run, I need the standard C library and I need puts. We're going to look at that in a second. And it produces an X-util format. And this is what you can actually XD. I'm an X-util format in Linux world and in 3DSD world and so forth. Its X-util format is called an L. It's not a mythical creature. It's the executable and linkable format. And in Windows, it's called the PE. I just blanked on what PE stands for. What's PE? Portable. Portable executable. That's it. On macOS, it's called MacO, m-a-c-h-dash O. And so forth. It's not a big deal. They're all more or less the same concept in a different way. Let's explore what that concept is. So let me produce it first. Honestly, I don't remember all of the flags to LV. It goes from this to an executable. I mean, I can get there, but it won't be runnable. No, I see. So we're going to cheat a little bit and we're going to let you figure out for us you see we'll call LV for us with the necessary stuff. And then we have our example, right? So the example, file of an application on my machine that has one type of file that we did. The example is an ELF, an executable and linkable format. It is 64 bits. I'm going to go through when a lot of this other stuff means. One thing that I'd point out is dynamically linked. I mentioned that on the last slide. We'll talk about that in a sec. And this can actually be executed. Now we've gone through the entire lifecycle of a program, a C program from, here is the source code that we wrote through the different compilation steps. Here is the executable. All right. ELF files, all sorts of shapes inside this. It used to be that the standard type of file you produced would define where your memory is going to get loaded. And you get loaded in a very specific location. And then control the jump there in the processor and you'll start executing stuff. Nowadays, ELF files are mostly relocatable executables. So they are loaded in a random location memory that's for stage team so that an attacker doesn't know where in memory the code is. This protects against classes of attacks that you load into later. ELFs aren't always executable. So in this case, it is an executable file because I can execute it. But an ELF file is also produced from a core file. When your program crashes, let's make my program crash. It's an easy way to make my program crash. Let's say I have an array and I do reference out of bounds part of the array. If I run crash, it'll cycle. When this happens, it can dump its memory into a file. And then I have this core file that's not executable. It is just a file that contains all of the code that was running when it crashed. And what's the next way to find the code? So ELF is a format. It's not intrinsically executable. The format itself is actually AR connection independent. We have the same format as in the same metadata but not the same content of an ELF file on x86, on on, on mid, on different processes. So let's look at some tools to look at what this ELF file actually is. One nice tool is read ELF. So read ELF-A will just basically dump all of the headers of an ELF file. Let's look through our ELF file here. So this is our ELF. The header that it has are all sorts of metadata that has to go through some interesting stuff. For one thing, you can see that it's a 64-bit ELF on that class metadata right here. So my system is 64-bit. There's a 64-bit thing. It is the location, the offset in the file, because this is a relocatable executable. The offset of the file that execution should begin is at this 0x hexadecimal 530. There's a bunch of section headers that disguise different parts of the file. One that we're interested in is dot text. Whoops. That text is the section of the file that contains the executable data. And you can see, of course, all the numbers are kind of confusing. This number here is where in the file this section is. And where in the file is that text? It's at 530, which is also where we should start execution. So 530 is actually the beginning of the text section. We start execution at the beginning of the text section. Let's see what else we got. We have section where there is data that is read-writeable, and then where there's data that's read-only, and so forth. Then we have another class of headers, program headers that describe how to load this executable into memory. And specifically, they say there are two parts of the executable and they're loaded into two different parts of memory, and so forth. It's not important right now, but this is how about creating a process with them. When you launch, for example, if you look at all of these headers, it loads stuff into memory and executes it. And the specific offset that it's sold to. Very cool. All right. Oh, and here's a typical of section. We talked about the text. That's the program code. I'm not sure why it's called dot text, but it is. I probably knew at some point. We have that data. I pointed that out. That's our read-writeable data, like global variables that have initial value that we need to write. There is read-only data, so that's where our hello world would be. And then there is something called the BSS, which is there for uninitialized data. So if you create a global variable that you don't set an initial value to, it will end up in the BSS. These two are separated because the BSS, you don't actually have to store anything in the file. You can say we have a section that is in memory of this size, whereas data that is initialized, and you can actually store it in the file. There's a way of reducing the file size. And then you have some preprocessors and post-processors in the sections.ini and .fini. If you have ever written like C++ and you have destructors with some of the fans' shared pointer functionality, modern T++, so your object will automatically be garbage-collected when you enter the program. You might end up with code in Fini, for example. Yeah. And then moving on, what is the actual code that runs in DITAM? So that is basically a data code for shipping code. The actual code is binary code. And we looked at the assembly, and we actually looked at the binary code in DITAM. And the binary code is architecture specific. So C itself is not necessarily architecture specific. It's 8,000 overall file. And I can compile it or any number of different architectures that can be violated for x86, 64-bit, like I did. That's my laptop. I can file it for art, which is what is the dot cross architecture that is running on your phones for MIPS, cross architecture that's running on most of your home Wi-Fi routers and all of this stuff. In this class, we're going to be talking about x86. x86 is kind of you trace its lineage one of the oldest processor architectures that we still use. And I would say it's the most common, but it's definitely the most common among your laptop, right, but of course, we have kind of the phone, we have kind of the larger license that are all running ARM or MIPS or some other crap. But x86 is the best one. It's very cool. x86 started out live way back when as these two processors, 8088 and 8086, but Intel made these processors with 16-bit registers, no memory management, et cetera, et cetera, no modern features, and then they gradually built them over the decades. 8386 is the first kind of recognizable modern processor that we have. It's about 32-bit CPU, so you can have 32-bits. 30-bit numbers, reaching about at one time. You can have up to 32-bit addressing of memory, so that's four gates of memory that you could have in a 32-bit computer. And they started adding stuff to that in late middle school or maybe in late elementary school, I don't remember, the Pentium came out. The Pentium was a big upgrade to the old three and six slide. Then there was the Pentium 3 in high school, all the cool kids had Pentium 3s. You could get something like up to 700 megahertz or something initially, and then they spread it out more and more. Then Intel kind of lost their way with the Pentium 4. Started wandering. Pentium 4 was very impressive. AMD came in during that era and revolutionized X86 by extending it to 64-bits. That's what all of your, many of you are running on your laptop, some of your laptops could be armed. And then Intel has kept up with the core processors. They left Pentium behind and they created core i3, i5, i7, i9. Not that those are chronological, but different classes of their processors. So this is kind of the X86 CPU family. X86 processors, all processors have a couple of sort of different concepts. Have you all taken into your organization? Yeah. There's at least one person that you guys know. They're going to very quickly talk about how a computer works. That's real quick. We have a computer. Let's say this is the computer. In a computer, you have a CPU. It's a very simple slide version, of course. You have memory, right? RAM. You have your hard drive or SSD or whatever. Disk. You load programs into RAM and then you have to do them. But what actually does the execution is your CPU, right? Your CPU goes instruction by instruction and then pulls it from RAM and then asks you to do it. The problem is if you've built a computer before, RAM is like this little stick that you plug into the motherboard and the CPU is like five centimeters away and the data can only travel from RAM to the CPU. Best case at the speed of light. But then there's all a whole bunch of other electronics in the way it's going everything down. Doing other logic and so forth. This is actually insanely slow. You have to read every instruction in your CPU from RAM and write all the effects out to RAM. The monitor and kind of internet automatically, that would be a problem with the monitors, right? This sort of thing. So, instead, there is a caching layer in the CPU and whereas pulling data from RAM could take hundreds or thousands of clock cycles, pulling data from the cache into the actual processing core takes let's say 10 clock cycles. So, data is pulled into RAM and then the CPU pulls it from RAM into cache, processor cache and writes out the results at its feature. But this is still too slow. This takes 10 of cycles when you say like, my CPU runs at log gigahertz. 10 of those cycles that we said we would be spent trying to get data from cache, that's not good. And so there's actually even closer to the processor pieces of data that are right on the processor and these are called registers. So the CPU pulls data from cache into these registers and acts on the registers directly. That is where your kind of the magic happens, right? And that's finally fast enough. This is simplified that modern architecture has multiple levels of caching. There's multiple cores. Each core has multiple execution, what are they called? The things that actually move the individual threads and so forth. But the important thing is how we understand the need for registers and what a register is. So register is basically this scratch space for a processor to perform operations on data. And here I have to take another kind of pause and mention that this is all about 32 bit x86. Since then over the last, I don't know, 10 years we have gotten squarely into 64 bit architecture. So all of your laptops unless they are from, you know, 2009 or something, I probably run in 64 bit x86 views. So this is a little outdated. That being said, I'm 32 bit x86. There are four general purpose registers. These guys that are just used for anything. So there's EAX, EVX, ECX, EDX. They all have some history to why they're called what they are. EAX started like in 8086 as a register called A. There's the accumulator, right? That's where you want to like add things to. So accumulator. And if you remember from computer architecture there are entire processor architectures that just have an accumulator. That's like the only way you can mess with data is to add a subtractive to the accumulator. When x86 or when, you know, 8086 moved to 16 bit, A got drawn into 16 bit register and they called it AX, A extender. And AX has two parts to it, AH, which is the high 8 bits and AL, which are the low 8 bits. So if you, you know, of course, draw it from right to left as a reasonable person. Here's your register. This is actually, we're going to rebel a little bit and talk about the full 64 bit register. This is 16 of it or 32 of it. This is 16, this has it, so there's AX, this is AH, this is AL, right? So if you stored number, you know, five, the very small number, it fits in basically three bits. It's 101. It would all of those by bits would go into AL. If you stored number 257, that's nine bits, 1-0-0-0-0-0-0-1. Right now you have AH and so forth. And you can actually interact with these or see if you can interact with these kind of separately. It can contract you to AH without affecting AL. Then when the upgrade to 32 bit happened, they said, okay, now we can have B-AX. So I guess AX probably stands for A extended, B-AX maybe stands for extremely extended, I don't know. So now we have AX and here is AX, A-H-AL still addressable by the CPU. Then AMD came along and they said, you know what, it's time to bring X86 64 bit era and they created 64 bit registers. And the 64 bit E-AX called R-AX, I guess really extended. And that's where we are today. I'm 32 bit of these four general purpose registers and two other ones that are basically general purpose and have some special meaning ESI and EDI. And as 32 bit and 64 bit in there is with the AMD created a ton of additional general purpose registers. And they just started numbering instead of giving you letters. So you have stuff like R10, R11, R12 and so on. Yeah. What is the, what is the AX or is it like two of them? A-H-AL and A-H-AL. Yeah. Lower and higher. When we made the jump from 8 bit to 16 bit, we kept the 8 bit register addressable as A-LOWER and add another 8 bits A-H-H-AL or you can add just both of them as A-H-ALs. So, yeah, and then as I mentioned, two registers are reserved for these sort of high bandwidth memory transfer with special instructions, ESI and EDI or modern times RSI and RDI. They're also usable, verified as general purpose registers. In fact on 64 bit, they're used to pass arguments with the function. And then there are a bunch of special purpose registers that are used for specific tasks. One of them is the stack pointer. It's called ESP on 32 bit, RSP on 64 bit. And let me just check if we're diving into stack. Okay, if we're diving into stack later, we're still doing it over here. Cool. Remember what the program stack is from computer architecture, kind of computer organization. All right, we'll probably not this week, unless maybe we can get there, but maybe next week we'll, next class we'll dive into that. And RVP slash EVP on 32 bit is the frame pointer. These help track the stack, which is where local variable is a function and basically the breadth from trail that allow the function to return to the function that call it is stored. So there are all these registers. There's some additional complexity. Back in 32 bit day was not actually viewed, I guess back in 16 bit days, really, memory wasn't viewed as a continuous thing. It was actually viewed as different segments that could be addressed in special ways. So in order to support that, there are these segment registers that were still used in 32 bit are no longer used in 64 bit that will allow you to address data with a certain law set for tracking where it was in memory. It's kind of a bummer that they got rid of these if they would have been potentially the useful security features in 64 bit processors. And then there are, there's this kind of execution lag registers that track the results of different comparisons that have different settings on how certain other instructions could work and should work and so forth. This was B flags. It was, of course, originally flags and then 32 bit if you can E flags and then 64 bit, what did it become? Exactly, and then the instruction pointer, right? I mentioned the CPU starts actually getting instructions one by one, pulling them from memory or really it pulls the memory to cache both the instruction from cache. It knows where the instruction is because there's a register that points to the next instruction that will get it. All the instructions that are in memory you saw an example that see that where you compile it, the first instruction that was going to be executed was that this hacks 530 offset. That was going to be that what the initial tracking pointer was set to and then it would just kind of take out for it slowly unless it was a program change execution in some major way. When does this pass out? Maximum. Okay, perfect. All right. And then, so that further instructions that modify the instruction pointer, you can call option that says instruction pointer to that function. You can return from a function and it sets it back to where you started. And then there are all sorts of other registers registers track floating point numbers to allow floating point operations. Registers nowadays there are insane amounts of kind of multimedia registers, registers that have 512 bits that you can use to encode and decode video very quickly and so forth. It's kind of the feature creep of x86 but also what makes x86 super cool. So data in x86 and then all five computer architectures nowadays is stored in little ambient. What does this mean? This means it is stored backwards. So you talked about how you had the number 287 you would have one in this higher age, right? And then 0101 in AL. And let's actually talk about the number 258 but then we can have a little bit of a difference here. So 258 in hex is 0x102. Yeah, I got confused for a second. 0x102, it's stored in binary like this. So there's 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 and then you're 0, 0, 0, 1, 0, right? When that is stored to memory, little ambient architectures will reverse the light. So when you store it in this memory, yeah, it's in the third board. When you store this in memory, erase this rx, when you store this in memory you would actually store it as in hex, let's say, right? This is 0102 where, how is it right here? You would actually store it at 0201. And when you read it in, you would reverse it. Why might this be? Those in my 1436 class are just shy, but know this. This is the way it is because when you move from, go there number one. One is back in the day, memory was stored on, yeah, back in the day, memory was stored on like tapes or some crap, right, like you're starting to stick to these. These tapes would take a long time to read. You would read them byte by byte or bit by bit. So the faster you could get to kind of the precision of the number, the better for a lot of algorithms, especially if the number was small. This is an anecdotal thing. What is, that's an anecdotal reason, another reason for a little idea is when you're talking about memory locations and you've suddenly gotten from eight bits to 16 bits and eight bits, of course, only have one byte, there's no idea as to speak of, you store the byte. And you got to 16 bits at the inside of our store, the byte backwards or not. And the reason why you would store them backwards is memory addressing between the modes becomes a lot easier. If you had an array of eight bit data that you would index into your old algorithm and then suddenly went to 16 bit data and you only still wanted to deal with it as a bit because that's how you want your source code, you could just double each address if you stored it backwards. If you didn't, you'd have to double it and add one. So, for better or worse, everything uses little engine nowadays, even architecture that started out not doing so have moved to little engines. So it used to be that PowerPC architecture that old school max or old school max ran on other architectures, middle school max ran on PowerPC. Now, Max used Intel, but even the PowerPC machines are now a little engine. It used to be big engine back in the day. MIPS used to mostly be big engine now, most variants of MIPS that are in use are little engine. That's a sweeping statement for most variants that I've ever seen. ARM, same sort of thing, there are big engine variants of ARM, with little engine variants that are competing them. It's an interesting phenomenon. Then, let's talk about how numbers are actually stored in these registers, specifically in terms of signness. So if you store numbers for any sort of mathematical algorithm, you'll eventually need to talk about negative number. We have interact with a negative number. Good. So, give me an example of a positive number. Three, awesome. Give me an example of a negative number. Negative, let's go with a negative five. All right, so you have three and negative five. How would you store three, let's say you have an 8-bit architecture. How would you store three in AL? So four zeroes. Four zeroes? Zero zero one one, awesome. That's three. So then the question becomes, how do you store negative five? There are two ways. One is, you say, all right, well, we only have eight bits. There's eight bits. Let's reserve the top bit, or being assigned it. Have you set that? So now this whole number is negative. A zero zero zero. And then zero one, zero one. Halfway point. That's negative five. So now we have this interesting problem. One is, we cut our total number of, like our maximum number that we can reason about impact. Because here we could go all the way up to 255, like we don't get it with all ones. Here we can. All right. And then there's another unfortunate thing that happens in this scenario. And we can see it if we try to add three and negative five. So if we add three and negative five, what is that bit once? If we add three and negative five, we get negative eight. That's not very good. And also if we add this negative eight to negative five, we get zero zero zero one one zero one, which is 12. Right. So this is awkward. Is it 12? Yeah, it should be good. That's awkward. So let's not do that. Let's come up with a better way to store negative numbers. And it turns out that that better way is called two's compliment. Two's compliment, actually one more thing. What happens to zero when we subtract one in our auto format? It rolls over, of course, and we'll get all ones. And zero minus one ends up being negative 127, right? So that's not good either. So some very bright people created something called two's compliment. And it said eight, one zero minus one, zero minus one is all ones. It's all ones. That's just gonna be negative one. All ones is negative one, that minus one, this is negative two, this is negative three, and so on. And it's weird to humans. Like this doesn't look like negative three to you, right? But it's actually very good for computers. Zero minus one becomes something that makes sense to the computer. And then you can add one to it again and it'll go back up to zero. And then if you have, let's just do negative one here. If you add three and negative one, what do we get? Spoiler alert, we get two, are you're supposed to get. So it's a weird thing for humans. One thing that keeps us, if this is one, the number is still negative, so you can easily, to me tell if the number is negative. But management operations now make sense again to the computer and the computer doesn't have to explicitly be trying to find our outside operator. Cool, thank you all. I guess I won't keep you the next class, but it's been a pleasure. I'll see you in my 466 when you're ready.