 A computer program ultimately is made up of machine code, binary instructions which are read and executed by the CPU. In the earliest days of computers, back in the 1940s, these machine instructions were input by flipping switches and moving vacuum tube plugs around. This of course was extremely clumsy because it required a lot of work by hand every time you wanted to run a program. So to fix this, computers began to store code on punch cards. A punch card is just a thick piece of paper with perforated holes and patterns to denote data, and programs can be stored as stacks of punch cards to feed into the machine. The process of creating these punch cards was still extremely tedious, and it was a huge headache when the stack got out of order, but at least now a program could be loaded multiple times without manually inputting each instruction one by one. In the 1960s, computers began to use terminals, devices which combined an electronic keyboard with some kind of text display. The earlier terminals printed out text on paper, later terminals introduced electronic screens, though usually these screens could only display monochromatic text, not graphical images. Also in the 60s, computers began to store code and data on electronic mediums, most commonly magnetic tape. In combination with electronic screen terminals, computer users for the first time could read and write text directly on the machine. So the question is, how does text entry help us write machine code? Well, one answer is to use a hex editor, a program which allows us to create and edit a file byte by byte using hex notation. While feasible, this approach is very impractical, even if you are extremely familiar with the machine code of the processor for which you're writing, it's just very taxing and cumbersome to write code directly in binary form. The better solution is to use what's called an assembly language. In an assembly language, each binary instruction is expressed in a more human-riddle way, usually as short mnemonics. For example, an ad instruction is commonly denoted with the mnemonic ADD. Of course, the machine can't execute text data, so this text has to be translated somehow into binary instructions. We call this translation process assembly, hence the term assembly language. Here's the Hello World program written in an assembly language. A Hello World program recalls simply prints the words Hello World on the screen. Understand that assembly code is always processor-specific because the binary instructions for one processor differ from the binary instructions for another. In fact, there really is no such thing as the assembly language, there are different assembly languages for different processors. Collectively though, they're often called assembly language. So there is assembly for x86 processor, assembly for ARM processors, for MISP processors, and so forth. This particular example is x86 code. Also understand that system calls are operating system-specific, so assembly code written to run on one operating system generally won't run on another. This code here is written to run on Linux, so even though it's x86 code, it won't run on Microsoft Windows. Like in all programming languages, the assembly code written as text by the programmer is known as source code. The computer of course can directly execute this code, rather the source code must be translated to machine code by a program called an assembler. For any particular processor, there may be multiple assemblers available, and the precise assembly language understood by one assembler may differ from the assembly understood by another assembler for that same processor. For example, one popular x86 assembler is Microsoft's assembler, often called MASM. Code written for the Microsoft assembler won't be understood by other x86 assemblers. So assembly code is not only processor and OS-specific, it's also assembler-specific. In any case, let's say our source code is written in a file called hello.asm. When we run this file through the assembler, what we get out is not an executable file, but rather what's called an object file. To produce an actual executable file, we run the object file through another program called a linker. The reason for this extra step is that in practice, we usually write the code of a program in multiple source files, files which we may wish to assemble independently. For example, if I've written my code in two files foo.asm and bar.asm, and I want to produce one single executable file here called baz, I first run each source file through the assembler independently, producing an object file for each, and then those two object files are run together through the linker, producing the executable. A linker is so named because it must resolve the links in code between the object files. Though we may write our code in separate source files, the code of our program is interdependent, such that code in one source file may invoke code written in another. For example, in foo, we might invoke a function defined in bar, but because the source files are assembled independently, the assembler can't know the actual address of the function in bar when assembling foo. What the assembler does then is leave a stub address where the function is invoked. Later the linker will fill in these stubs with actual addresses. Now we could just combine the linking and the assembly phases together in one single program so that we don't have to run two separate programs. However, we'd like to keep the processes separate so that we can minimize work when we make code changes by assembling only the source files we've changed, then linking those new object files with the other object files. By default, when a process runs, code in its address space comes just from the single executable file which launched the program. However, modern operating systems like Windows and Linux allow programs to link in code from other files during execution. These files of code are called dynamic linking libraries on Windows denoted with the file extension.dll and shared object files on Linux denoted with the file extension.so. More generally, they are sometimes called shared libraries. To make use of shared libraries requires instructing the linker to leave some stubs in the executable for the operating system to dynamically link at runtime. To create a shared library file, we simply run the object files we want to include in that library through a linker with the appropriate options. So you may be wondering, why would we want to use shared libraries and dynamic linking? Why not just statically link everything into one executable file? Well, one reason is to avoid producing needlessly large executable files. The concern these days, though, is not that the executable files will waste storage space – storage space today is very, very cheap – the real concern is memory usage. When multiple programs running simultaneously all make use of the same common code, it saves memory to load only one copy of that shared code. And this is exactly what modern operating systems allow us to do with shared libraries. When multiple processes all use the same shared library, the libraries included in each processes address space in their virtual memory, but the actual code is stored just once in physical memory.