Skip to main content
Systems Programming

Assembler and System Software Fundamentals

Published: 2026-08-20
Level: postgraduate
Audience: Postgraduate students in Systems Programming

Prerequisite Knowledge

This lecture builds on the following concepts from earlier lectures. If any feel unfamiliar, review the linked notes before proceeding.

Previously Covered in This Subject

  • System Software Versus Application Software -- covered in Lecture 1: Introduction to Systems Programming
  • Translators -- Compiler, Assembler, Linker and Loader -- covered in Lecture 1: Introduction to Systems Programming
  • Instruction Set and Processor Basics -- covered in Lecture 1: Introduction to Systems Programming
  • File and Process Foundations for System Calls -- covered in Lecture 1: Introduction to Systems Programming

13.1 Software Categories — System Software and Application Software

13.1.1 What Software Means in This Setting

Hook — why "software" is not just one program. You tap Gmail, Excel, or a game and see a finished tool — but underneath each tap is a list of instructions the processor actually steps through. Where does that list live, and why does the same idea get called a driver on one day and a product on another?

A software item is a collection of computer programs that runs on a laptop or other computer. The same idea appears under different names such as tool, driver, or product, but the core stays the same: a program, which is an ordered list of instructions. Each instruction tells the computer what to do and how to do it — move data, add values, test a condition, jump to another spot.

The actor that carries out those instructions is the processor. In the lecture the processor was framed as a digital circuit designed to handle a set of instructions — hardware that only understands machine code built from its instruction set (the vocabulary it was manufactured to decode). Everything a programmer writes, whether in Python, C, or assembly, must eventually be translated to that machine-form vocabulary before the processor can act. That translation chain is the spine of this whole lecture.

Intuition + analogy — recipe and kitchen appliance. Think of software as a recipe book and the processor as a kitchen robot that only understands stamped codes like 001 = "pick up bowl B" and 010 = "add". You might write "make cake" in plain English, but the robot cannot read it. A translator must convert your English recipe into the robot's stamped codes, line by line, or the robot does nothing. The mapping from your words to stamped codes is exactly the mapping from mnemonics like ADD to machine bits — and the set of stamps the robot ships with is its instruction set architecture. The analogy breaks in one place: a kitchen robot has a few dozen stamps, a modern processor has hundreds of distinct instructions, and swapping robots means swapping the whole stamp set.

Formalize — program, instruction, and machine code.

  • Program : a finite ordered sequence where each is an instruction.
  • Instruction : an operation code (what to do) plus zero or more operands (what to act on), with shape .
  • Processor: digital hardware that fetches , decodes using its instruction set , and executes it.
  • Machine code: the binary encoding of where every and operand has been replaced by the numeric patterns the processor decodes. Only this form runs directly.

The professor's line is the contract: write in any language, but execution requires the -specific binary.

One everyday illustration starts with . Here , , and are three data items that live at different addresses. Bringing the value of and the value of uses a move operation. Adding them uses an add operation. Storing the sum into uses a store or assignment operation. This split between move, add, and store shows how a single line that looks simple to a person becomes several steps that the hardware can act on.

Worked micro-example — as the hardware sees it.

Suppose lives at address , at , at , and the processor provides three real operations: LOAD (copy memory to register), ADD (register + memory), STORE (register to memory).

High-level:

Hardware steps:

  1. — copy value at into register . If , now .
  2. — add value at to . If , now .
  3. — copy to address . Now .

Answer: . Sense-check: a single + in C became three hardware steps because the processor cannot add two memory cells directly — it must bring each value close (into a register), combine them, then put the result back. That is why even "simple" lines need a translator.

Scope — when this view applies. This three-step split assumes a load-store architecture (most modern CPUs including x86 and ARM). On a pure stack machine the same might instead push , push , add top two stack items, store — different steps, same idea. The principle holds across all of them: high-level expressions must be decomposed into -legal micro-steps before execution. If the instruction set lacks a direct ADD memory,memory, the translator must emit the longer sequence — it cannot invent an instruction the hardware does not have.

Visual intuition: picture a three-layer tower. Bottom layer is bare silicon — the processor and memory chips. Middle layer is a scaffold labeled "system software" that knows how to bolt memory, schedule time on the CPU, and talk to devices. Top layer is a set of colored tiles — Gmail, Excel, Paint — resting on the scaffold. The x-axis is abstraction (from hardware-close on the left to human-close on the right), the y-axis is who you interact with. The takeaway: tiles never float; remove the scaffold and nothing on top can stand.

Pitfalls — names that trap beginners.

  • Calling a driver or a tool "not software" because it ships with hardware. It is software — the name changes with role, not with nature.
  • Thinking the processor runs your C text directly. It only runs the machine code that the toolchain finally produces. If you forget the translation step, every later section about assemblers and linkers will seem optional, when in fact they are mandatory.

Recap + bridge. Software is a list of instructions; the processor is the digital circuit that executes the -encoded binary form of that list, and already shows why translation is unavoidable. That sets up the next split: which software faces you directly, and which software faces the hardware so your software can run at all. Next is the application versus system divide.

Real-world and domain placement: every laptop you use today stacks these layers. When you double-click Excel, the operating system (system software) finds a free RAM region, and the translator chain (compiler → assembler → linker) has already converted Excel's source into the x86 or ARM binary your specific processor can decode. Systems programming lives in that middle scaffold — writing the very translators, loaders, and operating system pieces that make the top tiles possible.

13.1.2 Application Software — Programs Used to Produce an End Result

Application software is any software used in daily life to create a directly delivered result — something immediately useful outside the program itself. The discussion gave a set of concrete cases. A browser is used to browse the internet, watch videos, and open other web resources. A mailing system such as Gmail provides mail handling. A reader for PDF documents handles reading of documents. A drawing program such as MS Paint is used to create pictures. A presentation program such as PowerPoint is used to create slides. A suite such as Microsoft Office groups several such uses. The common trait is that the person opens the program to produce something that is immediately useful outside the program itself.

Intuition — the tile you touch. If system software is the scaffold, application software is the tile you actually paint, type, or watch video on. You choose it for the outcome (a slide deck, an edited photo, an inbox at zero), not for how it manages memory. That outcome-focus is the test: if closing the program erases the value you came for, it was probably an application.

Pitfall — "application means not important to the system." Students sometimes rank application software as less serious than system software. In engineering weight they are different jobs, not different importance. A browser may contain tens of millions of lines and rely on system software for every allocation and device access, but it is the reason the system was bought.

Visual intuition: imagine a market stall row. Each stall sells one finished good: one sells browsing, one sells mail, one sells PDF reading, one sells drawing. Customers walk to the stall for the good, not to admire the scaffolding behind it. Same row, different goods — that variety is the definition.

13.1.3 System Software — Programs that Operate the Hardware and Support Applications

Hook — who does the invisible work? When Excel asks for 200 MB of RAM or when two programs want the CPU at the same instant, who decides? No application decides that for itself — a different class of software does, and it is already running before you click anything.

System software is computer software designed to operate the computer hardware and to provide a platform for running application software. It works very closely with the hardware of the system.

Formalize — system software and the operating system.

  • System software : the set of programs that (a) abstract hardware (CPU, RAM, devices) and (b) provide services (allocation, scheduling, translation) on which application programs depend. Relation: runs on top of which runs on hardware .
  • Operating system (OS): the broadest member of . Core duties named in lecture: memory management, process synchronization, process management, and process scheduling. Operationally, when a package must run the OS identifies what must run, allocates memory for that program, and finds when a free slot exists for that program to get time on the CPU.

Other members of the same family were named together: assemblers, linkers, loaders, compilers, and editors. An editor is system software used to create notes: it lets the person create and change text that later becomes input to other tools. The point that ties these together is that application software cannot run smoothly without the support of system software that runs on the hardware.

Scope — what system software assumes. It assumes it has privileged access to hardware state (page tables, interrupt vectors, device registers) and that it is trusted to multiplex scarce resources. On a bare microcontroller with no OS, the same duties may be done by a tiny runtime or by the application itself — but the lecture's layered picture assumes a general-purpose machine where the OS is always present. Violate the assumption (run without an OS on a laptop) and every application must reimplement allocation and scheduling itself, which is why we do not.

Pitfalls.

  • Calling an editor "application software" because you type in it. In this taxonomy the editor is classified as system software because its output feeds the toolchain (source text → assembler/compiler), even though modern editors blur the line — the exam follows the lecture's classification.
  • Thinking the operating system's four duties are four separate programs. They are subsystems of one coordinating program that together implement "find, allocate, schedule, run."

13.1.4 How the Two Categories Relate

Application software needs system software whenever it is run. System software in turn runs on the hardware. The practical result is a layered view: hardware at the base, system software that knows how to use the hardware and that allocates resources, and application software that relies on that support to reach the user.

Picture the stack as:

Inline prose: each arrow is a dependency. translates and places code so can run it; requests services (memory, files, display) through and never touches directly in normal operation. The next sections follow the chain that turns a program written by a person into a form the hardware can run, where the assembler is one of the key system tools.

Recap + bridge. Application software delivers the end result you see; system software delivers the hardware abstraction and translation services that make that result possible. The layered dependency is the map for everything that follows: source → translator → object → linker → executable → loader → RAM → CPU. Next, that translation chain in full.

Real-world and domain placement: in systems programming interviews and in OS coursework, you are routinely asked to place a new tool into this stack — "is a device driver application or system software?" — and to trace a C file from editor through compiler and assembler to the running process. Getting the layer wrong predicts getting the whole pipeline wrong, so this first distinction is examinable scaffolding, not background.

13.2 The Translation Chain — From Source Program to Executable

13.2.1 Source Program, Object Program, and Executable Code

Hook — why "build" is not one step. You write hello.c and double-click an icon. Between those two moments at least four distinct programs touch your code. What are they, and why can none be skipped?

A source program is the program as written, either in a high-level language or in assembly language — the human-readable text you edit. A translator converts that source into a lower form. After that step the result is called an object program. The object program is very close to the form that can run. It is close to a machine language program, but it is not yet linked to the other modules of the same program or to the library routines it calls. If the overall program is split into modules or makes library calls (for example printf), those links are still missing — the object file contains slots where the external addresses should be.

The linker joins the object code with its dependencies, whether they are other modules of the same program or library routines. After linking, the result is an executable code or executable program, which is ready to run on that machine in the sense that all symbols are resolved — but it still lives on disk. The loader, which is itself part of the operating system and is system software, then helps load the executable onto primary memory, the RAM. The loader knows where the executable file is present on storage, maps it to a free memory location in RAM, and puts it there for execution. Only after that mapping can the operating system perform process scheduling, which points the CPU at the first instruction of the program and asks the hardware to start the sequence.

Formalize — the chain as a pipeline.

  • : human text (.c, .asm).
  • : near-machine code, not yet linked; one file per translation unit.
  • : fully linked, all external references resolved, still on disk.
  • : mapped to a free RAM region, then scheduled on CPU.

Skipping any stage leaves a hole: without the linker, external calls have no address; without the loader, code has no RAM home and the program counter has nowhere to point.

Scope — what "ready to run" means. The lecture's "executable" means link-complete, not running. Even a link-complete executable needs a loader to choose a base address and an OS scheduler to grant CPU time. On an embedded board with no OS, the loader step may be a simple flash copy done offline — but on a general-purpose OS the loader + scheduler pair is always involved. If you claim an executable runs without being loaded, you have skipped the address-binding step the hardware requires.

Visual intuition: imagine an assembly line with four stations. Station 1 (translator) rewrites your text into machine-ish fragments on trays (object files). Station 2 (linker) bolts trays together and screws in library shelves. Station 3 (loader) carries the finished cabinet to an empty bay in a warehouse (RAM). Station 4 (scheduler) hands the worker the bay number and says "start at shelf 1." The x-axis is time through the build; the y-axis is completeness. The takeaway: each station adds a binding that earlier stations cannot provide.

Pitfalls.

  • Calling the object program "the executable" because it looks like machine code. It is close, but unresolved externals make it unrunnable as-is — the linker gap matters.
  • Thinking the loader is the linker. The linker fixes which code goes together (on disk); the loader fixes where in RAM it sits at run time. They solve different binding problems.

13.2.2 Compilers, Assemblers, and Interpreters as Translators

Intuition — three kinds of translator, three habits. A compiler is a batch translator that rewrites the whole book before anyone reads it. An interpreter is a simultaneous interpreter who translates one sentence, speaks it, then translates the next. An assembler is the specialist who translates the dialect the hardware already almost understands (assembly) into its exact stamped codes.

Different source forms use different translators. A compiler takes a high-level program such as a C program and converts it to machine-level language. The description stressed that a compiler works on the whole program and produces the converted form in one shot, after which an exe-type file can be run — errors are reported together, optimization can look across the whole program. An interpreter instead works line by line. Python and the shell that runs shell scripts are the cases given. The interpreter converts a line to machine language and executes it, and when it meets an error it stops at that point — there is no separate exe, execution and translation interleave.

An assembler takes a program in assembly language and converts it to the lower form. In the overall flow that was sketched, a C program goes through a compiler to an assembly-language program, and that assembly program then goes through an assembler to an object program. The assembler step is why we study mnemonics like MOV — they are the vocabulary the compiler targets before the final binary is emitted.

A small flow recap expressed in words: high-level source such as C goes through a compiler to assembly language that contains the hardware-supported instructions. Those instructions use names such as MOV for move, IN for input, and ADD for add, but the machine can only understand zeros and ones. The assembler is then used to take that assembly-language program further down toward machine language. The object program that comes out is similar to machine language, and it still requires the linker to connect modules and library routines, producing the executable, which then requires the loader to get a place in memory.

Formalize — mapping source type to translator.

  • High-level (C) assembly object .
  • Assembly object .
  • Script (Python, shell) execute line-by-line; no persistent required.

Compiler and assembler are both "whole-program before run" translators; interpreter is "translate-and-run interleaved."

Comparison — compiler versus interpreter versus assembler:

Dimension Compiler Assembler Interpreter
Input High-level (C) Assembly (MOV, ADD) Script (Python, shell)
Granularity Whole program at once Whole program, but 1-to-1 mapping largely One line at a time
Output Assembly or object; persistent exe Object → executable via linker No persistent exe; direct execution
Error handling Reports many errors after full scan Reports assembly errors after scan Stops at first runtime error on that line
When to pick Need optimized, portable, reusable binary Need hardware-close control, or as compiler backend Need rapid, interactive execution

When to pick which: choose compiler/assembler when you need a reusable, optimizable binary; choose interpreter when you need immediate feedback and portability of source (not binary).

Pitfalls.

  • Saying "compiler produces the final executable directly." In the lecture's model it produces assembly; the assembler plus linker finish the job. Some modern toolchains hide the assembler step, but the logical stage remains.
  • Conflating interpreter's "line by line" with "slow therefore wrong." It is a different execution model, not a failed compiler.

13.2.3 Why a Loader Matters and How Much Is Loaded

The loader is initiated when the executable is to be run. It takes the executable and maps it to a free region of RAM. A question that often arises is whether the whole program must be in memory at once. For small programs that do work such as adding two numbers, evaluating a quadratic, or generating a Fibonacci series, the whole program is small and is loaded as a unit and then runs — the cost of demand handling would exceed the savings.

For larger programs that run into gigabytes, the system does not need to keep everything resident. Modern systems can load only the parts that are needed at a moment, which helps with multitasking and with keeping several programs in memory. The principle is demand loading / paging: keep the working set in RAM, bring the rest on fault.

Worked intuition — Microsoft Excel and macros.

  • Excel install: many features — cells, formulas, chart buttons, macros, COM add-ins, help data — total footprint can be gigabytes if fully expanded.
  • Common path: user opens a sheet, edits cells, inserts a chart. That code path touches the cell engine and chart renderer. The loader keeps that working set resident.
  • Rare path: user opens Developer → Macros → runs a VBA macro. Many users never touch this. The lecture noted: when macros are not in use, the code for macros is often not loaded at all. When macros are started, that code is brought into memory and then used.
  • Effect: without demand loading, every Excel instance would pay RAM for macro support even when 90% of users never use it; with it, RAM holds only what is actually executed, and the OS can keep more programs resident for multitasking.

Sense-check: if "whole program always loaded" were true, a 4 GB machine could not keep two 3 GB programs alive — but with demand loading it can, because each program's resident set at any moment is far smaller than its total size.

Scope — what demand loading assumes. It assumes virtual memory and a backing store (disk/SSD) so a fault can fetch the missing piece. On a tiny embedded MCU with no virtual memory and 64 KB of SRAM, the whole program must fit and be loaded — demand paging is not available. The lecture's "load on demand" picture is the general-purpose OS case.

Visual intuition: picture RAM as a small stage that can hold 5 actors. A large play has 30 actors. Instead of crowding all 30 on stage for the whole show, the stage manager (OS + loader) brings on only the scene's cast. Excel's macro actors wait in the wings until the macro scene is called — then they walk on.

13.2.4 Student Questions and Answers on the Chain

Q: What is the purpose of needing an assembler when we already have compilers and loaders, and does the loader have to bring the whole program into memory?

A: The assembler handles the step where the program is still in assembly language — the compiler covers high-level to assembly, the assembler covers assembly toward machine form, the linker joins modules and libraries, and the loader puts the resulting executable into RAM at a free spot. You need all three translators plus the linker because each fixes a different gap: compiler for abstraction, assembler for hardware-close names like MOV/ADD to bits, linker for cross-file addresses. Whether the full program is loaded depends on size. Small programs (add two numbers, quadratic, Fibonacci) are loaded as a whole. For large programs the loader and operating system can keep only the needed pieces resident, adding more as required — which is why a feature such as macros in Excel may stay out of memory until it is started. Deduplicated: several students asked variants of "do we need all these tools?" and "is loading all-or-nothing?" — one canonical answer covers both.

Recap + bridge. The chain splits labor: translator creates near-machine code, linker completes it, loader places it, scheduler runs it; compiler/assembler/interpreter differ by input and granularity; and the loader may be whole-program or demand-driven depending on size. That chain produces a concrete artifact — the executable — whose portability is the next question: can that artifact travel to a different machine?

Exam note: expect to order the chain, to name which tool fixes which gap (compiler = HL→asm, assembler = asm→bits, linker = join, loader = place in RAM), and to explain Excel macros as the demand-loading case.

Real-world and domain placement: in build systems (gccasld) and in DevOps (container images contain linked executables that a loader still must place), this chain is the daily pipeline. Systems programmers debug it directly — "undefined reference" is a linker complaint, "segfault at load" is a loader/placement complaint — so naming the right stage points to the right fix.

13.3 Hardware Platforms, Instruction Sets, and Why Executables Do Not Travel

13.3.1 Instruction Set Architecture Tied to the Processor

Hook — why "it runs on my laptop" is not a guarantee. The same file that opens perfectly on one machine refuses to even start on another. The difference is not the file being corrupted — it is the processor inside expecting a different language.

Each processor comes with its own instruction set architecture (ISA). That architecture defines which instructions the processor supports and can execute — the vocabulary plus the encoding, register set, addressing modes, and side effects. The examples given for mnemonics included MOV, IN, OUT, ADD, SUB, and MUL. The same program that is meaningful on one machine may be meaningless on another when the two machines do not share the same set of instructions, just as an English sentence is meaningless to a reader who only knows Hindi script.

Formalize — ISA as the hardware contract.

  • ISA for processor family : the set that hardware decodes. Example: may exist in but not in , or it may exist in both with different binary encodings.
  • Executable : a binary whose every instruction . If , processor cannot decode it — it faults or traps, even if the file itself is intact.

So executability is a relation, not a property of the file alone: is executable for , not executable in the abstract.

Scope — when ISA matters and when it does not. ISA binding matters for native (ahead-of-time) binaries — C/C++ executables, bare assembly programs. It does not apply the same way to source scripts (Python) which are re-translated on the target, or to bytecode that is translated at run time by a virtual machine. Confusing those two classes is the root of the "why doesn't my exe travel?" misconception.

13.3.2 x86 and ARM as Two Families

Two broad families were used as contrast.

Two families, two vocabularies.

  • x86: the family built around Intel parts such as Pentium, dual core, and Xeon, with the name kept from the series 8086, 186, 286, 386, 486, 586 where 586 was Pentium and the naming later changed while the x86 label stayed. Found in most Windows laptops and many servers.
  • ARM: a different architecture found in mobile devices, in Raspberry Pi, and in many phones that run Android. Designed for different power and cost trade-offs.

Each family has its own set of instructions that it can run, and the two sets are not compatible with each other — not just different names, different binary encodings and often different instruction formats and register files.

Real-world: x86 appears in Windows laptops and many servers through Intel parts such as Pentium, dual core, Xeon, and the historical 8086 through 586 line. ARM appears in smart phones, many Android devices, and boards such as Raspberry Pi, with its own instruction set that differs from x86. A program compiled for one must be rebuilt for the other — there is no shortcut of "rename the file."

13.3.3 What Happens When an Executable Is Copied to a Different Family

A simple question was asked: a source program is turned into executable code on an x86 machine, where the executable runs with no problem. That same executable file is copied to an ARM machine and an attempt is made to run it there.

Worked thought experiment — copying the executable.

  • Step 1: On x86 laptop, compile sum.c with an x86-targeting compiler. Produce sum.exe where, say, the add at the core is encoded as bytes 01 D8 (x86 encoding for ADD EAX, EBX).
  • Step 2: Copy sum.exe verbatim to an ARM Raspberry Pi via USB or network. File bytes are identical.
  • Step 3: Ask the ARM CPU to fetch the first instruction. The CPU reads 01 D8 and tries to decode it using . That bit pattern either means a completely different ARM instruction or is illegal, so the CPU raises an illegal-instruction fault and the OS kills the process.
  • Result: does not run. To run on ARM, you must re-translate: sum.c sum_arm whose add is encoded with the ARM pattern (e.g., E0800001), which does decode.

Sense-check: file copy preserves bytes, but execution requires decode by the target ISA — and the bytes were written for a different decoder. Copying without re-encoding is like playing a Hindi vinyl on an English-only phonograph and expecting words.

The answer stressed that this direct copy does not work. The executable built on x86 contains instructions confined to x86. An ARM processor cannot recognize those instructions and cannot run them. To run on the new hardware the source must again go through a translator that targets that platform, producing a new executable that fits the instruction set of that machine.

Pitfall — "exe means universal." Students sometimes treat "executable" as "finished and portable." The lecture's correction: executable means link-complete for one ISA, not portable across ISAs. Portability must be achieved by a different mechanism (source portability or bytecode + VM).

13.3.4 How Java Bytecode Differs From a Direct Executable

Java was presented as a contrast that often causes confusion. When Java source is built it is not turned into a directly runnable machine executable. It is turned into bytecode, which appears as a file with a .class extension. Bytecode by itself does not run directly on the hardware. To run, it needs a runtime environment. That runtime is the JRE, which runs a Java Virtual Machine, often shortened to JVM. The JVM is specific to the hardware. The JVM for a Linux machine matches that machine, the JVM for Windows on x86 matches that machine, and the JVM for ARM matches that machine. Because the JVM is present in a hardware-specific form, the same bytecode file can be carried to any of those JVMs and run there. In that run the JVM takes each bytecode instruction and translates it on the fly to the instruction form that the underlying hardware supports.

Formalize — two portability models.

  • Native model (C): Copying to fails.
  • Bytecode model (Java): is portable; each is hardware-specific. Portability is paid for by a per-platform translator that ships with the platform.

That difference explains the earlier conclusion. A C executable holds the final machine code for one hardware family and stays tied to that family, while a Java class file holds bytecode that travels because the translation to hardware form happens inside the JVM at run time.

Visual intuition: picture two shipping strategies. C ships a finished chair built for door width — if the next house has door width it does not fit. Java ships a flat-pack plus an on-site assembler (the JVM) who builds the chair to fit whatever door is there. Flat-pack travels; finished chair does not, unless you rebuild it.

Scope — what bytecode trades away. The on-the-fly translation costs time and needs the JVM installed. If no exists for a target, bytecode cannot run there either. "Write once, run anywhere" holds only where a matching JVM has been ported — which today is many places, but it is a porting effort, not magic.

13.3.5 Student Questions and Answers on Platform Dependence

Q: An executable built on x86 is platform independent because it is already an executable file. If that file is copied to an ARM-based machine or phone, will it run without problems?

A: No. An executable built on x86 holds instructions for the x86 instruction set. An ARM processor follows a different architecture with its own instruction set, and the two families are not compatible. The x86 machine instructions are not recognizable on ARM and will not run there. To run on ARM the source must be translated again using a translator that targets ARM hardware. Java bytecode is the case that does travel: code is built to bytecode in the form of a .class file, which is not directly runnable, and that bytecode runs on any machine that has the matching JVM, because the JVM is specific to the hardware and translates the bytecode to hardware instructions on the fly. The runtime environment is what makes that portability possible. Why the confusion felt plausible: "executable" sounds finished, and the file does run fine on x86 — so students infer universality. The correction: "executable" means finished for that processor's decoder, not for every decoder.

Q: Can you restate what the question was about running the same executable on a different machine? (Deduplicated — same confusion point as above, asked again with Raspberry Pi named explicitly; merged here with frequency noted: several students asked this variant.)

A: The question was whether a source program converted to executable on an x86 Windows laptop with a Pentium, dual core, or Xeon processor can be copied to a smart phone or a board such as Raspberry Pi that uses an ARM processor and still run. The answer is that it cannot run as is, because the hardware platform and the processor family define the instruction set. The executable that runs on x86 must be rebuilt for ARM, while Java bytecode can run on either because the translation is done by the hardware-specific JVM at execution time. The two models differ in when the final translation happens: C at build time (so the binary is frozen to one ISA), Java at run time inside the JVM (so the class file stays neutral).

Recap + bridge. An ISA is a per-processor contract; x86 and ARM are incompatible contracts, so a native executable is bound to the contract it was built for and must be rebuilt to move. Java sidesteps this by shipping bytecode plus a per-platform JVM that translates on the fly. That frames why the assembler matters next: it is the stage that commits text like STL to the specific numeric codes of one chosen ISA.

Comparison at a glance: C executable = fast, no extra runtime, but rebuild per ISA. Java bytecode = one file travels, but needs a matching JVM and pays a translation cost at run time. Pick native when you control the target hardware; pick bytecode/VM when you must span many hardware types with one distribution artifact.

Real-world and domain placement: this is why app stores ship multiple APKs or fat binaries (one build per ISA), why cloud builders do cross-compilation (--target aarch64-unknown-linux-gnu versus x86_64), and why "it works on my laptop" is a systems bug until you name the ISA you built for. In the domain of systems programming, ISA awareness is the first line of portability engineering.

13.4 Assembly Language — Mnemonics, Symbolic Operands, and Instruction Format

13.4.1 The Role of Assembly Language in the Chain

Hook — the last human-readable step before bits. A compiler has turned if (x==0) into something like COMP ZERO and JEQ ENDFIL. Those English words still mean something to you, but to the CPU they are nonsense — it only reads ones and zeros. What bridges that final gap?

Assembly language sits between high-level source and machine zeros and ones. After a compiler has produced assembly, that assembly already uses the instructions the target hardware supports — it is already ISA-specific — but still in text form. The assembler then carries the translation further down. The machine itself can only understand binary, so names such as MOV cannot stay as text; they must have a numeric machine equivalent that the decoder was wired to recognize. That final text-to-number step is the assembler's core job, and this section names the two kinds of text it must convert.

Formalize — where assembly sits.

Assembly is already hardware-family-specific (x86 assembly differs from ARM assembly), but still symbolic. Machine code is the same logic with every symbol replaced by its numeric encoding. No further new logic is added here — only the exact bit patterns the hardware expects.

Visual intuition: assembly is an architectural blueprint where rooms are labeled "kitchen" and "bedroom"; machine code is the same blueprint where every label has been replaced by GPS coordinates. A builder (the CPU) can only navigate by coordinates, but humans edit by names until the last moment.

13.4.2 Mnemonics, Also Called Operation Codes

Intuition — a nickname for an action. A mnemonic is a short, memorable name that tells the processor what to do with the operands it is given — "STL" is easier to remember than 00010100_2. Think of it as a nickname for a stamped action code: the nickname is for humans, the stamp number is for the machine, and the assembler's dictionary translates between them.

A mnemonic is a short name that tells the processor what to do with the operands it is given. The same idea is also called an operation code, shortened to opcode. Cases named in the lecture included STL for store, LDA for load address, STA for store address, LDCH and STCH for load and store character, LDX for load X, TIX for test index, COMP for compare, JEQ for jump on equal, J for jump, and JSUB for jump to subroutine. Each processor family defines which mnemonics exist, through its instruction set architecture — you cannot invent FOO and expect a stock CPU to know it. The assembler converts each mnemonic to the machine language value that the hardware recognizes. That numeric value is often shown in hexadecimal in notes and tables before it is expanded to binary for the hardware, because hex is compact for humans and maps cleanly to bits (one hex digit = four bits).

Formalize — mnemonic to machine value.

The assembler keeps a table (OPTAB, seen later) that implements the map . The lecture reconstructed it as: Concrete bindings used throughout this lecture (SIC/XE family in the running example, reconciled against the textbook's Fig. 2.1/2.2): Each of those hex values is finally represented as binary zeros and ones for execution, with a hex digit mapping to four binary bits. For example (since , ), and .

Reconciliation note: the textbook's OPTAB for SIC (R7, Chapter 2) lists the same six opcodes with identical hex values; the lecture's values match the standard form, so no discrepancy correction is needed — notation is kept as etc. to match the slides.

Scope — whose mnemonics? These six opcodes are specific to the SIC/XE illustration used for the COPY program. x86 and ARM use different mnemonic sets and different hex encodings — LDA at will not mean the same on an x86 chip. The mapping is always per-ISA; the mechanism (mnemonic → table → bits) is universal.

Pitfalls.

  • Calling a mnemonic "the instruction." The mnemonic is only the opcode field; a full instruction also needs its address/operand field (next subsection).
  • Expanding hex to binary digit by digit incorrectly. Remember each hex digit is exactly four bits, so a two-digit hex opcode is one byte = eight bits. is not "14 in binary" — it is .

13.4.3 Symbolic Operands and Why They Need Addresses

Along with the mnemonic, many instructions name a symbolic operand — a data variable or label given by name, such as , or labels such as RETADR for return address, RDREC for the read-record subroutine, WRREC for the write-record subroutine, LENGTH for the length of the record, BUFFER for the buffer that holds characters, THREE, ZERO, ENDFIL for end-of-file handling, and CLOOP for the copy loop. The name by itself is not enough for the hardware. The processor has no dictionary for "RETADR" — it only knows numeric addresses. The assembler must turn the name into the equivalent machine address where the item lives, so the hardware can access the value at that address at run time.

Formalize — symbol to address.

The assembler maintains a second table (SYMTAB) implementing . In the running example: An instruction's symbolic operand is a slot for the numeric address that SYMTAB will supply once that label's location is known.

Visual intuition: symbols are like contact names in your phone. You tap "Mom" — the phone dials a number. The name is for you; the number is for the network. SYMTAB is the phone's contact list, and the assembler's job is to replace every tapped name with the right number before handing the call to the network.

Scope — when a symbol needs no address. Data directives like EOF BYTE C'EOF' define a constant value, not a code address. The symbol EOF still gets an address (where its bytes live), but its operand in LDA EOF is that storage address, not the characters themselves. Confusing "value of EOF" (the bytes ) with "address of EOF" ( in the table) is a common slip.

13.4.4 How an Assembly Instruction Is Built

An assembly instruction brings the two parts together. The opcode field holds the machine value for the mnemonic, and the address field holds the machine address for the symbolic operand. In the listings shown, an instruction such as STL RETADR becomes a pair followed by , where is the address of RETADR. An instruction such as JSUB RDREC becomes followed by , where is the address of the RDREC subroutine. An instruction such as LDA LENGTH becomes followed by . A compare such as COMP with zero uses with address , and a jump on equal to ENDFIL uses with . A simple jump to CLOOP uses with . Each of those pairs is the proper machine instruction format that the hardware can step through — opcode says what, address says where.

Formalize — instruction format.

In the SIC format illustrated, each instruction is three bytes (24 bits): one byte opcode + two bytes address.

Example: Similarly: The collection of these formatted triplets, in program order, is what later becomes the Text records of the object program.

Worked construction — three instructions side by side.

  • STL RETADR: opcode (from OPTAB), address (from SYMTAB) → bytes 14 10 33.
  • LDA LENGTH: opcode , address → bytes 00 10 36.
  • JEQ ENDFIL: opcode , address → bytes 30 10 15.

Each is six hex digits = three bytes = 24 bits. Final bytes are as above. Sense-check: changing only the symbol (RETADR→LENGTH) changes only the last four hex digits; the leading or stays tied to the mnemonic, as expected from the two-field format.

Pitfalls.

  • Writing the address in decimal in the object code. The object program shows addresses in hex (1033, not 4147). Mixing bases makes every address wrong.
  • Thinking the assembler invents the address. It does not — it looks up the address that the location counter assigned to that label during Pass 1.

13.4.5 Data Constants Need Internal Form Too

The source also contains data constants that may be given in decimal, hexadecimal, or binary form as written. All of those must be turned into the internal machine representation, which is binary. The text example for the symbolic constant EOF was shown as a character constant whose internal form is the hex bytes spelling "EOF" in display code, later expanded to binary.

Formalize — constant conversion.

  • Source form: EOF BYTE C'EOF' (characters E,O,F) or THREE WORD 3 (decimal 3).
  • Assembler action: map each character via ASCII/display code to its hex byte, or map decimal to hex , then to binary.
  • For "EOF": so the three-byte field is 45 4F 46. The same rule turns decimal 3 into 00 00 03.

The principle: whatever base the programmer used to write the constant, the stored form is always the hardware's binary — hex in the listing is just a human-friendly view of those bits.

Recap + bridge. Assembly language is human-readable ISA text; the assembler does two lookups for every line — mnemonic via OPTAB to an opcode byte and symbol via SYMTAB to an address — then concatenates them into the fixed instruction format, converting any written constants to binary along the way. Those per-line translations are the five assembler functions examined next.

Exam note: expect to produce the three-byte hex for a given mnemonic + symbol pair by combining the opcode table and the address table. Keep the two tables separate in your head: OPTAB never changes per program, SYMTAB is built per program.

Real-world and domain placement: even when you never write assembly by hand, compilers emit it and debuggers show it. Reading 14 1033 as "store to RETADR" is the skill that lets you interpret a crash dump, a disassembly view, or an embedded system's hex listing — the same literacy that systems engineers use to bring up a new board before any high-level toolchain is fully working.

13.5 Core Functions of the Assembler — The Five Translation Steps

13.5.1 Overview

Hook — five jobs, but one hard one. If assembling were just "replace words with numbers," a single scan would finish it. The assembler actually does five jobs, and four of them are indeed one-scan trivial — the fifth is why assemblers need two passes.

The assembler is asked to do five tasks in order to turn the assembly listing into the sequence of object codes that forms the object program. The discussion walked through these steps using the running example from the COPY program and the associated tables. Naming them first helps you see where each later detail slots in:

  1. Convert mnemonic opcodes to machine equivalents.
  2. Convert symbolic operands to machine addresses.
  3. Build machine instructions in proper format.
  4. Convert data constants to internal representation.
  5. Write the object program and the assembly listing.

Steps 1, 3, 4, 5 are local to one line. Step 2 is global — it needs a name whose definition may be pages away — which is the difficulty flagged at the end of this section.

13.5.2 Step 1 — Convert Mnemonic Operation Codes to Machine Equivalents

Formalize — step 1 as a table lookup.

For each source line that is a machine instruction, the assembler hashes the mnemonic and reads . That entry holds the machine value (often printed in hex) and, on format-variable machines, the instruction length .

In the COPY illustration: The assembler does not compute these numbers — it looks them up. The textbook's OPTAB (R7 Table 2.1) matches these six values exactly, so the mapping is reconciled and stable.

The first job is to look at each mnemonic and place the machine language equivalent for that opcode. Using the OPTAB mapping above, STL maps to , LDA maps to , JSUB maps to , COMP maps to , JEQ maps to , and J maps to . The slide's verbal anchor — "convert mnemonic operation codes to machine language equivalents" — was stated while pointing at pairs where the left column held STL, JSUB, LDA and the right columns held 14, 48, 00, emphasizing that the left side is for humans and the right side is for the decoder.

Pitfalls.

  • Treating directives like START or BYTE as opcodes with a machine value. They have none — step 1 is skipped for directives, and the opcode column in the listing shows the directive name itself.
  • Hex-case confusion: written as 3c or 3C is the same value; case does not change the bits .

13.5.3 Step 2 — Convert Symbolic Operands to Machine Addresses

Formalize — step 2 as a symbol-table lookup.

For each instruction with a symbolic operand , the assembler needs . In COPY the bindings used are: The instruction format then becomes .

The second job is to replace each symbolic operand name with the address where that symbol lives. The cases used were RETADR at , ZERO at , THREE at , LENGTH at , BUFFER as the buffer area, RDREC at , WRREC at , CLOOP at , and ENDFIL at . In the instruction format this is the address field that follows the opcode byte. So STL RETADR becomes , JSUB RDREC becomes , LDA LENGTH becomes , COMP ZERO becomes , JEQ ENDFIL becomes , and J CLOOP becomes . The lecture drew attention to how a symbolic name such as RETADR is not usable by the hardware until it is replaced by its numeric address — the name is a human convenience, the address is the machine requirement.

Worked mapping — six instructions, one rule.

Assembly Opcode (OPTAB) Symbol → Address (SYMTAB) Object (hex)
STL RETADR 14 10 33
JSUB RDREC 48 20 39
LDA LENGTH 00 10 36
COMP ZERO 28 10 30
JEQ ENDFIL 30 10 15
J CLOOP 3C 10 03

Each row is opcode byte followed by two address bytes. Final hex as in table. Sense-check: only the last four hex digits change when the symbol changes — the leading byte is locked to the mnemonic, confirming the two-field format.

Scope — whose addresses? These addresses are the SIC addresses assigned when START is and the COPY code occupies up to about before the BUFFER gap. On a different program with START , every SYMTAB value shifts — the mechanism (symbol → address via SYMTAB) stays, the numbers change.

13.5.4 Step 3 — Build Machine Instructions in Proper Format

With the opcode value and the address value known, the assembler builds the full machine instruction in the format that the hardware expects. The format shown is opcode byte followed by address bytes, often described as one byte for the opcode and two or three bytes for the address in the illustrations that displayed three hex bytes per line such as . The collection of those formatted instructions in order is the core of what will be written into the object program.

Formalize — concatenation with fixed width.

For the SIC three-byte format: So 14 10 33 is not 14 plus decimal 1033 — it is 14 plus the two-byte hex 10 33 where 10 33 is the 16-bit encoding of address .

On SIC/XE with variable formats, step 3 also selects the right format (1 to 4 bytes) using the OPTAB length field — but the COPY illustration stays uniformly three bytes.

Visual intuition: step 3 is stamping a luggage tag. Left side is stamped with the action icon (opcode), right side with the destination room number (address). The tag always has the same two fields, in the same order, so the handler (CPU) knows where to look.

13.5.5 Step 4 — Convert Data Constants to Internal Representation

Data constants that were written in source as characters or numbers are expanded to the internal form. The example traced was EOF, where the written characters "EOF" become the internal hex string and the corresponding binary pattern . The same idea applies to numeric constants that may be written in decimal in the source but must be stored as binary for the machine.

Formalize — source base to stored bits.

  • Character constant C'EOF' → per-character ASCII/display code → hex bytes → bits. E.g., 'E' = 45_{16}.
  • Hex constant X'F1' → directly hex → bits.
  • Decimal constant WORD 5 → decimal as a 3-byte word in SIC → bits. The assembler does the base conversion; the programmer chooses the convenient base to write in, the machine always stores bits.

13.5.6 Step 5 — Write the Object Program and the Assembly Listing

After the previous steps the assembler writes two outputs. The object program is the ordered sequence of all the object codes, organized into records that the loader can later map into memory (Header, Text, End — detailed in the next section). The assembly listing shows four columns together: location, label, instruction including any assembler directive, and argument. In the sample listing the leftmost column gave the location such as , the next column gave the label such as COPY or CLOOP, the next column gave the instruction such as STL or LDA or an assembler directive such as START, and the last column gave the argument such as RETADR or LENGTH. Those four columns together are the listing that was shown beside the object codes — the human-readable audit trail where each line's translation can be checked.

Pitfall — listing versus object program. The listing is for you (with labels and mnemonics still visible). The object program is for the loader (only H/T/E records with hex). Submitting the listing where the object program is expected is a frequent assignment slip.

13.5.7 A Note on Ordering and the Difficulty in Step 2

The discussion noted that every step except the second one can be handled by reading the source one line at a time and acting at once. Step 2 is the exception because an instruction may name a symbol that has not been defined yet in the reading order. That situation is called a forward reference, and it forces the assembler to take more than one pass over the source. The remaining sections explain that problem and the two-pass response.

Recap + bridge. Steps 1, 3, 4, 5 are line-local lookups and formatting; step 2 is non-local because a symbol may be defined later. That single fact — forward references exist — explains why the next three sections are needed: what forward references look like in COPY, how two passes solve them, and what tables make both passes fast.

Exam note: expect to apply all five steps to a short listing that mixes true opcodes with directives (BYTE, WORD, RESB, RESW, START, END). The route is: opcode from OPTAB, address from SYMTAB, format as three bytes, constants to hex bits, then emit H/T/E records plus the four-column listing.

Real-world and domain placement: modern toolchains (like gas or nasm) still conceptually do these five jobs; they just hide Pass 1/2 inside one invocation. Understanding the split helps you read the error messages: "undefined symbol" is a step-2/SYMTAB complaint, "invalid opcode" is a step-1/OPTAB complaint, and a wrong hex dump is usually a step-3 format mistake.

13.6 A Complete Assembly Example — The COPY Program with RDREC and WRREC

13.6.1 What the COPY Program Does at a High Level

Hook — one program, three jobs. Copying a file sounds like one action, but the running example splits it into a controller that decides, a reader that fills memory, and a writer that empties it. Why split, and how do the three share the same buffer?

The running illustration is a pseudo-code program that copies a record from an input device to an output device. It is structured as a main copy loop plus two subroutines. The main part is named COPY. Its logic is: save the return address, call the subroutine RDREC to read one record into a BUFFER, check the length of the record that was read, and then branch. If the length is zero the program calls WRREC to write an end-of-file marker. If the length is greater than zero it calls WRREC to write the record just read to the output. It then returns to the top of the copy loop, written as CLOOP, and repeats. When the overall copy work is done it loads the saved return address back and returns to whoever called COPY, which was described as a main program that called COPY as a routine.

Think of COPY as a foreman who never touches the material himself — he sends the reader crew, inspects what they brought, then sends the writer crew, and loops until there is no more material.

Formalize — COPY as a control loop.

COPY:  save RETADR
CLOOP: call RDREC          // BUFFER ← input, LENGTH ← bytes read
       if LENGTH == 0 → ENDFIL path (write EOF marker, then finish)
       else           → call WRREC   // output ← BUFFER[0..LENGTH-1]
       goto CLOOP
ENDFIL: write EOF marker via WRREC
       restore RETADR, return

LENGTH = 0 is the sentinel for end-of-file in this illustration; any positive LENGTH means "a real record arrived."

Scope — what this example is and is not. This is a SIC illustration program (R7, Fig. 2.2) designed to show assembler behavior, not a production file-copy utility. Real file copy handles blocking, errors, permissions, and buffering far beyond one BUFFER and one LENGTH. The mechanisms (call, indexed store/load, length check) transfer; the surrounding OS work does not.

13.6.2 The Assembly Listing Frame for COPY

The listing frame shown has location, label, instruction, and argument — the classic four-column audit trail. The header line shown is COPY START 1000, where COPY is the program name, START is a pseudo-instruction, and signals that the program begins at location . Following lines are of the form:

  • location label none instruction STL argument RETADR producing object
  • location label CLOOP instruction JSUB argument RDREC producing
  • location instruction LDA argument LENGTH producing
  • location instruction COMP argument ZERO with ZERO at producing
  • location instruction JEQ argument ENDFIL with ENDFIL at producing
  • location instruction JSUB argument WRREC producing
  • another J instruction to CLOOP with , and so on through the subroutines.

The same pattern repeats for the other mnemonics: LDA always shows , STL always , JSUB always . The address field changes to match the argument name.

Why the addresses step by 3. Each SIC instruction in this program is three bytes, so the location counter advances as: Hence where and . Hex addition with carries: , . This regular stepping is the audit trail that later object records verify.

Worked frame — tying listing to bytes.

Loc Label Instruction Arg Bytes (hex) Why
1000 (none) STL RETADR (1033) 14 10 33 opcode 14 + addr 1033
1003 CLOOP JSUB RDREC (2039) 48 20 39 call reader
1006 LDA LENGTH (1036) 00 10 36 get length
1009 COMP ZERO (1030) 28 10 30 compare length vs 0
100C JEQ ENDFIL (1015) 30 10 15 if zero → EOF path
100F JSUB WRREC (2061) 48 20 61 else write record
1012 J CLOOP (1003) 3C 10 03 loop forever

Sense-check: every opcode byte matches Section 13.4's table, every address byte matches the SYMTAB built later, and locations advance by exactly 3. If a row violated any of those three, it would be a step-1, step-2, or LOCCTR bug respectively.

Exam note: expect a question that gives a similar listing (with START, a few real instructions, some BYTE/WORD/RESB lines, and END) and asks for the matching object code. The method is the five steps from the previous section plus the directive handling from the next.

13.6.3 RDREC — Reading a Record Into a Buffer

Intuition — two roles, one register. Inside the read loop, is a finger pointing to the next empty slot in the buffer. After the loop, the same is the answer — the length of the record. One variable, two meanings, separated by the moment the loop exits.

RDREC is a subroutine that brings one record from an input device into memory. The processor hardware contains fast memory elements called registers. In this program two registers are used, named and . The description of RDREC proceeds as:

Clear and clear to zero. Both registers are set to .

Enter a loop named RLOOP. Read one character from the input device, where the input device was illustrated as a keypad, into register . So after the read, holds the next input character.

Decide whether the character marks the end of record. The end of record is shown as the value or by an marker in the teaching diagram. If the character is not the end marker, store that character into the BUFFER at the index held in . In notation, . Then increment by one, , so now points to the next free slot in the buffer for the next character.

Check whether is still less than the maximum length allowed. If go back to RLOOP and fetch another character. If has reached the limit, fall out of the loop. At that point holds the count of characters that were stored, which is the length of the record. That value is written to the symbol LENGTH by store to LENGTH, often noted as STX LENGTH. Then return from the subroutine to the caller. The record itself now sits in BUFFER, and its length sits in LENGTH, and both will be used by the write side.

Formalize — RDREC as a traceable loop (procedural spine: Purpose → Inputs/Outputs → Steps → Trace).

  • Purpose: fill BUFFER[0..] with input bytes and set LENGTH.
  • Inputs: input device (keypad/file), MAXLEN (buffer capacity).
  • Outputs: BUFFER containing the record, LENGTH =\) number of bytes stored, X = LENGTH` on exit.
  • Steps:
  1. .
  2. RLOOP: . If → go to 4.
  3. if goto RLOOP.
  4. return.

The key invariant inside the loop is and equals bytes stored so far.

Trace — reading "Hi" with MAXLEN = 10.

  • Start: .
  • RLOOP 1: read 'H' ( ) → ; not EOR → BUFFER[0]=48_{16}; → loop.
  • RLOOP 2: read 'i' ( ) → BUFFER[1]=69_{16}; → loop.
  • RLOOP 3: read EOR marker → exit loop without storing.
  • Exit: STX LENGTHLENGTH=2, BUFFER = [48,69]. Result: LENGTH=2, BUFFER="Hi".

Sense-check: if input were empty (first read is EOR), the loop stores nothing and LENGTH becomes 0 — the sentinel COPY tests for.

Visual intuition: BUFFER is a row of pigeonholes, is a sliding pointer. Each non-EOR character drops into the hole under the pointer, then the pointer slides right. When the stop card appears, you stop and count how far the pointer slid — that count is LENGTH.

13.6.4 WRREC — Writing the Buffer to the Output Device

WRREC is the mirror routine that takes what is in BUFFER and shows it on an output device, illustrated as a display. Its logic is:

Clear to . So at entry.

Use a loop named WLOOP. Get the character from BUFFER indexed by , so the character is . Write that character from position to the output device. Then increment , . Then check whether is still less than LENGTH. In symbols, if go back to WLOOP. This repeats so that the record is walked from the first character to the last, one position per iteration, until the whole record has been displayed.

Formalize — WRREC as the indexed walk.

  • Purpose: display BUFFER[0..LENGTH-1].
  • Steps:
  1. .
  2. WLOOP: if goto WLOOP.
  3. return.

Invariant: is the index of the next character to display; loop runs exactly LENGTH times.

Trace — writing "Hi" with LENGTH=2.

  • Start: .
  • WLOOP 1: ('H') → display 'H' → ; → loop.
  • WLOOP 2: ('i') → display 'i' → ; false → exit.

Result: display shows "Hi", on exit. If LENGTH were 3 for "EOF" (45 4F 46), the loop would run three iterations, one per byte — matching the BYTE constant discussed earlier.

Pitfalls.

  • Off-by-one: using would read one past the buffer. The correct test is strict .
  • Forgetting to clear on entry. If WRREC is called twice, stale from the prior call would start mid-buffer.

13.6.5 How the Pieces Fit Together

Putting COPY, RDREC, and WRREC together gives a full path: COPY saves the caller return link, calls RDREC to fill BUFFER and LENGTH, examines LENGTH against zero, calls WRREC either with EOF or with the buffer contents, loops back to CLOOP, and when that outer work is done restores the return address and returns. The buffer that RDREC fills is the same buffer that WRREC reads, which is why the address of BUFFER appears in both subroutines and why the gap that was seen between the main code ending near and RDREC starting at was attributed to the space reserved for BUFFER between those addresses.

That gap is not wasted — it is the reservation for BUFFER itself (and nearby data like EOF, ZERO, RETADR, LENGTH). The assembler reserved it via RESB/RESW directives, so the loader later knows to leave that range uninitialized but allocated. The text records reflect the same gap: they are contiguous within each block and jump over the reserved area.

Scope — who owns BUFFER? BUFFER is shared mutable state between RDREC and WRREC, accessed via its fixed address. That sharing works here because calls are sequential (COPY never calls both at once). In a concurrent system with interrupts, unprotected sharing would need synchronization — the lecture's single-threaded model assumes you never are inside RDREC and WRREC simultaneously.

Visual intuition: draw COPY as a roundabout with three exits — exit 1 to RDREC (inbound lane fills BUFFER), exit 2 through a LENGTH checkpoint (0 → EOF branch, >0 → data branch) to WRREC (outbound lane drains BUFFER), and a loopback arrow to CLOOP. BUFFER sits at the center island, touched from both sides.

13.6.6 Student Question on the Subroutines

Q: Are the input and output side roles clear when we trace RDREC and WRREC through BUFFER and LENGTH?

A: Yes. RDREC is the input side. It clears and , loops reading a character into , storing into , bumping , and looping while . When the loop ends, is the length and is stored as LENGTH, and BUFFER holds the record. WRREC is the output side. It clears , loops getting , writing it to the display, bumping , and looping while . COPY is the control side that decides after the read whether LENGTH is zero, and then calls WRREC to handle either the end-of-file marker or the real record, looping back to CLOOP each time and restoring the return address when the overall run ends. Teaching move: the professor repeated the trace explicitly pointing at and the two tests to cement that RDREC and WRREC are not two unrelated loops — they are mirror walks over the same storage, one filling and counting, the other draining and counting.

Recap + bridge. COPY orchestrates: STL RETADR → loop JSUB RDREC → LDA LENGTH → COMP ZERO → JEQ ENDFIL / JSUB WRREC → J CLOOP. RDREC is the indexed fill with as both pointer and eventual length; WRREC is the indexed drain counted by LENGTH; they meet at BUFFER, whose reservation explains the address gap between and . Next is the vocabulary that makes that gap possible: the directives that tell the assembler how much space to reserve and which bytes to pre-fill.

Real-world and domain placement: this read-buffer-write pattern is the skeleton of every copy utility (cp, dd), every network proxy (read from socket, write to socket), and every embedded sensor loop (read ADC into buffer, process, write to UART). The register names change (ARM uses R0R12), but the indexed-buffer plus length-exchange idiom is the portable core that systems code reuses.

13.7 Assembler Directives — Pseudo-Instructions that Guide the Assembler

13.7.1 What Directives Are

Hook — instructions that never run. In the listing you see lines like COPY START 1000 and BUFFER RESB 4096 that look like instructions but produce no opcode byte. What are they, and why does the assembler need them?

Assembler directives are also called pseudo-instructions. The wording "pseudo" was explained as meaning "false" instruction. They are not instructions that the processor will execute. Instead they give information to the assembler itself that helps the assembler carry out the translation — where to start, where to stop, how many bytes to set aside, what constant to pre-fill. Because they are not processor instructions they are not turned into machine language opcodes. The actual instructions are still the mnemonics such as STL, LDA, and JSUB, while the directives sit among them and steer the handling of addresses and data. Think of them as stage directions to the translator, not lines spoken by the actor.

Formalize — directive versus instruction.

  • Instruction: where → produces object bytes .
  • Directive: where → no opcode byte; instead the assembler updates LOCCTR, reserves storage, or emits data bytes directly.

Mixing them in the same four-column listing is intentional — the same location column tracks both, but only instructions contribute an opcode field.

13.7.2 START and END Mark the Program Boundaries

START and END as the frame.

  • COPY START 1000 : program name is COPY, starting address operand is . Effect: and the header record's starting address is set to . The program name in columns 2–7 of the Header also comes from this line's label.
  • END or END FIRST : marks end of source. Effect: stop assembly; operand (if present) names the first executable instruction for the End record. In the listings the first line uses START and there is a single END near the close of the whole source; even when only a fragment is shown, the END would be present for a full program.

Without START, the assembler would not know where LOCCTR begins and every SYMTAB address would be wrong. Without END, it would not know when to emit the final H/T/E records.

START tells the assembler that the assembly program starts at a particular place. A listing line such as COPY START 1000_{16} was shown, where is the address where the program begins. The value for the location counter is tied to the START operand. END marks where the end of the program occurs and signals the assembler that there are no more lines to process. In the listings the first line uses START and there is a single END near the close of the whole source, with the understanding that even when only a fragment is shown the END would be present for a full program.

Scope — START versus load address. START sets the assembled addresses (where the assembler thinks the program will live). The loader may relocate them at run time on systems that support relocation — but in the simple SIC model here, START's address is the literal load address in the Header and End records. Confusing "assembled at 1000" with "always loaded at 1000 on every OS" is only safe in this simple loader model.

13.7.3 BYTE — Generate Character or Hexadecimal Constants

BYTE directs the assembler to generate a character or hexadecimal constant that occupies as many bytes as needed to represent the constant as written. Syntax in SIC:

  • C'EOF' → three characters → three bytes 45 4F 46 (ASCII/display codes for E,O,F).
  • X'F1' → hex pair F1 → one byte F1.
  • X'05' with odd digits may be padded per implementation — the lecture noted each BYTE line is handled the same way: write exactly the bytes the constant denotes.

In the listing that was examined, BYTE appears as a directive in the instruction column with a constant as its argument, and the effect is that the given characters are expanded to their internal hex and binary bytes. The same directive was shown repeated to underline that each BYTE line is handled in the same way — one BYTE per constant, each contributing its bytes to the next Text record.

Formalize — BYTE length rule.

So C'EOF' (k=3) advances LOCCTR by 3, and the three bytes appear verbatim in the object Text. No opcode is emitted — the bytes themselves are the data.

13.7.4 WORD — Generate One Word Integer Constant

WORD directs the assembler to generate one word integer constant. Where BYTE works by the byte, WORD groups several bytes together to form one word. The width used in the discussion was that a byte is eight bits, while a word is sixteen bits made from several bytes together — in SIC the standard word is three bytes (24 bits), and the lecture simplified to "word is several bytes, in the illustration sixteen bits." When WORD appears in the instruction column its argument is taken as an integer constant for that word. Example: THREE WORD 3 → value as a three-byte word 00 00 03, which occupies one word in the object and advances LOCCTR by the word size.

Formalize — BYTE vs WORD.

  • BYTE: variable length, byte-granular, for characters or hex strings.
  • WORD: fixed length of one word (SIC: 3 bytes / 24 bits), for integer constants. So RESW 1 and WORD 3 both move LOCCTR by one word, but WORD emits 00 00 03 while RESW emits nothing (it only reserves).

Keeping them distinct prevents the common error of using BYTE where WORD's fixed-size alignment matters.

13.7.5 RESB and RESW — Reserve Space Without an Initial Value

RESB stands for reserve byte. It directs the assembler to reserve the indicated number of bytes for the data named by the label on that line. RESW stands for reserve word. It directs the assembler to reserve the indicated number of words for the data, where each word is several bytes. So if a line gives a label and RESB 1, one byte is set aside; if it gives a label and RESW 1, one full word (three bytes in SIC, described as sixteen bits in the simplified width used by the illustration) is set aside. The discussion used these to explain how BUFFER and similar areas get space.

Formalize — reserve directives.

  • BUFFER RESB 4096, no object bytes emitted; loader will reserve 4096 bytes at that address.
  • LENGTH RESW 1 (one SIC word), no bytes emitted.
  • Contrast with BYTE/WORD which do emit bytes for the same LOCCTR advance.

That split — advance LOCCTR by the same amount, but emit versus not emit — is what creates the gaps seen in Text records: reserved areas produce no Text bytes, so the next Text record jumps over them.

Worked LOCCTR walk with directives.

Assume START and the main code ends at before data:

  • EOF BYTE C'EOF' (3 chars) → LOCCTR ? In the textbook table EOF lands at — exact offset depends on prior instructions, but the rule is +3.
  • THREE WORD 3 → +3 bytes → gap widens by one word.
  • BUFFER RESB 4096 → +4096 bytes → LOCCTR jumps from near to near (4096, so — the exact jump seen in the Text records).
  • RDREC label then lands at , which is why its Text record starts at .

Sense-check: the jump is , exactly the BUFFER size — confirming "gap equals reservation."

13.7.6 How Directives Appear in the Four Column Listing

In the four-column listing the directive appears where an instruction would appear. For example a line may show a location, then a label such as BUFFER, then a directive such as RESB, then an argument such as a numeric count. The same column layout is shared with real instructions such as LDX or LDA, and the assembler distinguishes the two cases: when the middle column holds a directive the line controls allocation or boundaries, when it holds a true opcode the line will produce a machine code byte.

Real-world: byte and word widths described here match the way memory allocation directives are used to size character buffers and integer work areas that the later execution will read and write. Sizing matters — a RESB 4096 that is too small truncates long records silently, while one that is too large wastes RAM but is harmless.

Pitfalls.

  • Writing RESB and expecting bytes in the object program. Reserved space has no bytes in Text — only an address gap. Only BYTE/WORD emit.
  • Forgetting that directives still consume LOCCTR. A missing +3 for a WORD throws every later SYMTAB address off by one word.

Visual intuition: directives are the architect's notes on the blueprint — "leave this many meters empty," "pour concrete here with this mix." The notes never become a wall themselves, but they shift where every later wall stands.

Recap + bridge. Directives are the assembler's control language: START/END frame the program, BYTE/WORD pre-fill data bytes, RESB/RESW reserve empty space, and all four advance LOCCTR while only the first two emit bytes. That emitted-versus-reserved split is exactly what shapes the object program's record structure next.

Exam note: be ready to compute LOCCTR after each directive line and to state whether a line contributes bytes to a Text record or only a gap.

Real-world and domain placement: every ISA's assembler has the same directive family under different names (.byte, .word, .space, .org in GNU as; DB, DW, RESB in NASM). Recognizing "this line is a directive, not an instruction" is the first step to reading any disassembly or linker map in systems work.

13.8 The Object Program — Header, Text, and End Records

13.8.1 Why the Object Program Needs Records

Hook — who tells the loader where to put things and where to start? The assembler has produced a bag of hex bytes. Without a manifest that says "this bag belongs to program COPY, starts at , is bytes long, and should enter at ," the loader would have to guess — and guessing with RAM placement corrupts memory.

After assembly the collection of object codes must be written in a form that the linker and the loader can use. That form is the object program, and it is organized into three types of records: a header record, one or more text records, and an end record. Each record type has a fixed column pattern that indicates its meaning, its addresses, and its content. The record structure is the handoff contract between assembler, linker, and loader — it answers "what, where, how long, and where next" for every chunk.

13.8.2 Header Record — Name, Starting Address, and Length

Formalize — Header as the table of contents.

Columns are 1-indexed, contiguous, fixed-width (R7, §2.1):

  • Col 1: H (record type).
  • Cols 2–7: program name, six characters (space-padded). Sample: COPYCOPY plus two blanks in the six-char field (shown as COPY in compact listings; the field width is what matters).
  • Cols 8–13: starting address of object program, six hex digits. For COPY: 001000 (padded to six digits).
  • Cols 14–19: length of object program in bytes, six hex digits. For COPY: 00107A_{16}.

So a header is 19 columns total, structured as bytes of manifest text (not program bytes). The hex values inside are later expanded to binary for storage.

The header record carries an overview of the program. Column 1 holds the letter indicating a header record. Columns 2 to 7 hold the program name, six bytes in the design that was illustrated. The sample name COPY, written as C O P Y, fits inside those six bytes with room to spare. The discussion noted that while the sample design uses six bytes for the name, modern practice lets names be longer, up to 255, so the design shown is tied to the sample program.

Columns 8 to 13 hold the starting address, again six bytes expressed in hex. For the COPY work the starting address is , which appears in the record as 00 10 00 in hex display as 001000. That value comes directly from the operand of the START line COPY START 1000, where was the value after START for the location counter.

Columns 14 to 19 hold the length of the object program in bytes, written in hexadecimal. In the sample that header displayed the length as 00 10 7A, that is 00107A. Put together the header looks like a single line that starts with , then carries the name field with COPY, then 001000, then 00107A. Using the textbook's figure (R7 Fig. 2.3a compact form: HCOPY 00100000107A), the same fields are present with visual separators added for reading. The earlier miscounting of column widths noted in class comes from remembering that each field size already includes the counting offset: 2 to 7 is bytes, 8 to 13 is , and 14 to 19 is , so the line length of 19 is consistent.

Worked header — field math.

  • Program name field length: . COPY (4 chars) plus 2 blanks still occupies 6.
  • Start address field: 001000 means decimal start.
  • Length field: 00107A → decimal ? Wait, compute properly: . The lecture displayed 00107A as the program length in bytes — the exact decimal is rarely needed on the exam, but the hex length is.

Result: header reads H COPY 001000 00107A (with padding), i.e. 19 columns.

Pitfalls.

  • Calling the address field "six bytes" meaning six program bytes. It is six hex-digit columns representing three bytes of address, written as text. The loader parses the hex text and converts to binary.
  • Miscomputing column-inclusive counts: 2 to 7 is six, not five — the lecture explicitly flagged this as a common miscount.

13.8.3 Text Records — The Ordered Object Codes With Their Addresses

Formalize — Text record fields (R7, §2.1).

  • Col 1: T.
  • Cols 2–7: starting address for object code in this record, six hex digits.
  • Cols 8–9: length of object code in this record in bytes, two hex digits (so max FF bytes, but the SIC format limits to 30 bytes here).
  • Cols 10–69: object code, represented in hexadecimal, two columns per byte of object code, up to 60 columns → up to 30 bytes of code.

Counting the size helps: columns 10 to 69 inclusive contain column positions. Each byte is shown with two hex digits, so one byte occupies two positions. A machine instruction in the illustration occupies three bytes, that is six hex digits. So one text record can hold at most instructions. That is why the illustration repeatedly showed ten instructions per text record.

A text record carries part of the actual translated code. Column 1 holds the letter indicating a text record. Columns 2 to 7 hold the starting address in this record, in hex. Columns 8 to 9 hold the length of the object code in this record in bytes, also in hex. Columns 10 to 69 hold the object code itself.

In the worked illustration the first text record after the header started at address 001000 and showed length . The value expands to decimal as bytes, which matches ten instructions at three bytes each. The object bytes in that record included ten successive codes such as 14 10 33, 48 20 39, 00 10 36, and so on, in that order. The next record started where that one ended. Adding the start and the length gives the new start: . That next record showed length , which is bytes, again holding its next group. Adding once more, , though the illustration noted that the address stream as shown jumps to 002039 for the next group because the intervening locations are not text but are taken by the BUFFER area that sits between the end of the main COPY code near and the start of the RDREC subroutine at . After the jump, the pattern continues: start 002039 plus length gives 002057, and start 002057 plus length gives 002073, each group holding its next ten codes until the program reaches the area for WRREC at . That interleaving shows that a text record is not only the contiguous run of instructions; it is also interrupted where directives reserve space.

Worked address progression — two jumps, with arithmetic.

  • at 001000, len : next start . Check: (hex add, no carry beyond). Verifies: ten 3-byte instructions = 30 bytes = length .
  • at 00101E, len : next would be — but BUFFER's 4096-byte reservation () consumes 001033 through 002038, so the assembler skips to 002039 for the next Text start (the gap is not wasted — it is the loader's reservation for BUFFER).
  • at 002039, len : next . Again ten instructions.
  • at 002057, len : note . Next would be . Each step is start + length.

A special case was shown at the close of the text area: one of the last text records has length , which is bytes, holding exactly three object bytes in the design used there? In the textbook figure the final partial records reflect alignment after data directives like EOF/THREE/buffers — the same "length counts bytes, not instructions" rule applies.

Sense-check: the total program length in the Header (00107A) should equal the sum of all Text lengths plus reserved-but-unemitted space accounted for in LOCCTR — the loader uses that to know how much address space to reserve, not just how many Text bytes to copy.

Best-practice visual from the lecture: the slide wrote column 1 is T, 2 to 7 starting address in this record, 8 and 9 length in bytes, 10 to 69 object code and traced adding to to reach , then adding to that to reach the next start, and later aligning and . Following that addition trail is the fastest way to verify a Text dump on the exam.

Another useful identity that was traced: the length of the object code in a record that holds ten three-byte instructions is bytes, which written in hex is because . That conversion from decimal byte count to hex length appears directly in the 8 to 9 columns, and the reverse — hex , hex , hex , hex — was drilled.

Scope — whose columns? The 1/2–7/8–9/10–69 column split is the SIC textbook format (R7). Real toolchains use binary object formats (ELF, COFF) with section headers, not letter-prefixed text lines — but every format must still convey the same four facts: name, start, length, and per-chunk bytes+address.

Visual intuition: a Text record is a moving van's manifest: "van leaves warehouse at address , carries bytes, here they are in order." The loader parks each van at its stated address. The BUFFER gap is an empty lot the assembler flags as "reserved — do not park, but do not build over."

Pitfalls.

  • Reading length as "30 instructions." It is 30 bytes — ten instructions in this 3-byte/instruction program.
  • Forgetting that columns 10–69 are hex text, two characters per byte. 14 occupies two columns but is one byte of code.

13.8.4 End Record — Where Execution Begins

Formalize — End record.

  • Col 1: E.
  • Cols 2–7: address of the first executable instruction in object program, six hex digits. For COPY that address is 001000, the same value that appeared in START and in the header.

So the end line appears as E followed by 001000. Written fully it is a short line such as E 001000, with the understanding that the address field is six hex digits as in the header. That final address tells the loader and the operating system where to set the program counter for the first step. In the textbook, E001000 is shown; when END has an operand like END FIRST, that operand's address is used instead — but COPY uses the START address.

The end record marks the close of the object program and indicates where execution should start. Column 1 holds the letter . Columns 2 to 7 hold the address of the first executable instruction, in hex. For COPY that address is , the same value that appeared in START and in the header. So the end line appears as followed by . Written fully it is a short line such as , with the understanding that the address field is six hex digits as in the header. The loader reads this to initialize the program counter; without it the OS would not know which address to jump to.

13.8.5 Putting Header, Text, and End Together

Taken together the object program appears as a file with a single header line beginning with H, several text lines each beginning with T and giving its own starting address and byte count and the hex bytes, and a single end line beginning with E with the entry address. The discussion showed a compact listing that uses the field demarcations H T T T T T E in that order — header, five-ish text groups covering main code, the gap, RDREC, WRREC, and the final tail, then end — which is the same ordering seen in inner sections of the teaching figure (R7 Fig. 2.3).

13.8.6 Worked Example of Record Fields

One worked group was matched line by line:

  • Header COPY from col 1 , cols 2–7 name COPY, 8–13 start 001000, 14–19 length 00107A.
  • Text followed by ten object codes each three bytes beginning 14 10 33, 48 20 39, and so on through the main loop.
  • Text continuing the code.
  • Gap for BUFFER explaining the jump in addresses from near to .
  • Text , then , then continuing through RDREC and WRREC.
  • End giving the first executable instruction address .

Each hex byte in those records, where a byte holds two hex digits and each hex digit maps to four binary bits, is what the loader finally expands to binary for placement in RAM.

End-to-end sense-check — does the length field match the code?

Ten 3-byte instructions = 30 bytes → length . The record starting at 001000 holds ten such instructions, so is correct. A record with seven instructions would be — matching the second record's . Ten indicators: count instructions, multiply by 3, convert to hex, compare with columns 8–9. If they disagree, either a BYTE/RESB length was miscounted or a gap was not skipped.

Result: field math closes; the dump is internally consistent.

Recap + bridge. An object program is a loader-readable contract: one H with name/start/length, a run of T's each with start/length/hex-bytes (max 30 bytes here, gap-aware), and a closing E with the entry point. Mastering the column widths and the hex length conversion () plus the start+length→next-start addition is the mechanics behind every "given listing, write object program" exam question. The next question is why that listing cannot be produced in a single left-to-right scan.

Real-world and domain placement: modern executables (ELF on Linux, PE on Windows, Mach-O on macOS) still carry the same ideas under different names — header with entry point, section table with virtual addresses and file lengths, then raw section bytes. The SIC H/T/E letters are pedagogical simplifications; the concepts (address, length, entry point, gap for bss) are exactly what readelf -h and objdump show.

13.9 Forward References and the Need for Two Passes

13.9.1 What Counts as Forward and Backward

Hook — why does the very first line already cause trouble? At location the listing says STL RETADR, but RETADR lives at , thirty-odd bytes later. How can you translate a line that names a future you have not yet read?

A forward reference is a symbolic operand that names a label whose definition appears later in the source order — the use precedes the definition. A backward reference names a label that has already been defined earlier — the definition precedes the use.

In the COPY listing the instruction STL RETADR at location near names RETADR that lives at . That name has not been seen when the line is first read, so it is a forward reference. Similarly JSUB RDREC names RDREC at while still at the early part of the file, LDA LENGTH names LENGTH at , and the later COMP and JEQ lines name ZERO and ENDFIL before those symbols have been met. All of those are forward references — the assembler meets the need before the supply.

By contrast a jump such as J CLOOP at location near points back to CLOOP at . That target has already been seen, so it is a backward reference and the address is already known when the line is scanned. Backward references never block a one-pass translator; forward references always do until extra bookkeeping is added.

Formalize — reference direction.

For a source order where each label is defined at the line that bears it:

  • Use of symbol at line is forward if the definition of is at with (future).
  • It is backward if (past).
  • In COPY: for STL RETADR, for RETADR RESW 1 → forward (). For J CLOOP at 1012, CLOOP at 1003 → backward.

The five forward examples from the lecture: RETADR (), RDREC (), LENGTH (), ZERO (), ENDFIL (). The backward example: CLOOP () seen before its later use at .

13.9.2 Why Step 2 Is the Hard Step

The five assembler tasks were recalled: 1 convert mnemonics to values, 2 convert symbolic operands to addresses, 3 build formatted instructions, 4 convert constants, 5 write the object program and listing. All of those except step 2 can be handled by a straight sequential scan that reads one line at a time and acts immediately — mnemonics are in OPTAB already, formats are known, constants convert locally, and the listing can be written line by line.

Step 2 cannot, because a forward reference needs a label that the sequential scan has not met. The assembler does not know where RDREC lives when it first sees JSUB RDREC, so it cannot fill the address field at that moment. It could leave a hole and hope to patch it, but without a second scan it would have to know how far ahead to look — and that distance depends on directives like RESB 4096 that shift everything.

Scope — when is step 2 trivial? Only in two special cases: (a) no forward references exist (program is written definition-before-use everywhere, like J CLOOP was), or (b) the assembler is allowed to emit fixup records and let the linker patch forward addresses later. The SIC two-pass design assumes neither — it resolves everything before emitting text.

Pitfalls.

  • Thinking "the assembler can just look ahead one line." Forward targets can be thousands of bytes away (BUFFER's 4096-byte gap pushes RDREC far ahead). A one-line peek does not help; you need to have scanned the whole program's label definitions.
  • Confusing forward with "undefined." Forward symbols are defined — just later. Undefined symbols never appear as a label at all and remain an error even after two passes.

Visual intuition: imagine copying a book where footnotes refer to page numbers that have not been numbered yet. You cannot fill "see page 47" until you have paginated the whole book. Forward references are exactly those future-page footnotes — step 2 is the footnote-filling step.

13.9.3 The Two Pass Response

To solve that problem the assembler makes two passes over the source. The idea stated was: pass one notes all label definitions and assigns addresses to them, so that by the close of the first pass every label has a known address including the ones that were forward at first sight. Pass two is the actual translation pass, forming object codes and writing the listing, where the address that was missing in the first encounter is now filled in because the table already holds the future definition. That is why the assembler for this class is called a two-pass assembler. One pass collects the future, the second pass fills the blanks.

Formalize — two passes as collect then fill.

  • Pass 1 (define symbols): walk source , maintain LOCCTR, and on each line that has a label store . No object bytes need be complete — only LOCCTR and SYMTAB matter. By END, SYMTAB contains even the forward labels: EOF at , THREE at , ZERO at , RETADR at , LENGTH at , BUFFER's block, RDREC at , WRREC at , etc.
  • Pass 2 (assemble instructions): walk again , this time translate each line: , (now always hit because SYMTAB is complete), emit Text bytes.

Forward references are forward only in time — after Pass 1 they are all "backward" in table space.

Q: The assembly listing at the very first line already names RETADR and RDREC. Those addresses are at the far end of the listing. How does the assembler handle them without knowing where they are?

A: Those are forward references. They are symbols defined later in the listing, after the lines that use them — STL RETADR at uses RETADR defined near , JSUB RDREC at uses RDREC at . A single sequential scan cannot resolve them because the target has not been seen. The assembler handles them by taking two passes. In pass one it walks the whole program, gives each line its location, and records every label name with the address where that label appears, such as EOF at , THREE at , ZERO at , RETADR at , LENGTH at , BUFFER around its reserved block, and RDREC at and WRREC at . In pass two it walks the same program again and now every symbol, even those that were forward before, has a table entry, so it can place for RETADR, for RDREC, and the others, into the address fields and produce the complete object codes. The professor stressed this Q&A explicitly pointing at the first two lines to make the abstract "future definition" concrete.

Recap + bridge. Forward means use-before-definition, backward means definition-before-use; COPY's early lines are riddled with forward references (RETADR, RDREC, LENGTH, ZERO, ENDFIL) while J CLOOP is the backward counterpart that needs no extra work. Because step 2 needs future labels, a one-pass scan cannot fill its address fields — hence the two-pass contract: Pass 1 builds the full SYMTAB by scanning for labels and bumping LOCCTR, Pass 2 consumes that table to fill every address. Next is exactly what each pass does line by line and what it reads and writes.

Exam note: be able to label any mnemonic symbol pair as forward or backward given a listing with locations, and to state in one sentence why forward forces a second pass while backward does not.

Real-world and domain placement: the forward-reference idea generalizes beyond assemblers — single-pass compilers must also forward-declare functions (int foo(); in C) or make two passes over declarations, and linkers perform a similar "collect symbols then resolve" loop across object files. Recognizing "this needs a definition that is not yet seen" is the systems diagnosis for why a tool needs another pass or a fixup table.

13.10 The Two-Pass Assembler — What Pass 1 and Pass 2 Do

13.10.1 What Is a Pass

Hook — what does "two passes" literally mean? It is not two assemblers — it is one program that reads your source file twice, doing a different job each time, and leaving a note for itself between reads.

A pass is one full read over the source program from the first line to END. The two-pass assembler does that work twice, with different roles for each sweep.

  • Pass 1 is the address-collecting pass. It assigns a location to every line and remembers where every label landed.
  • Pass 2 is the code-emitting pass. It uses the remembered locations to stamp the final bytes.

The work that was just described for forward references is the driver for this separation — without it, Pass 2 would have holes where forward symbols belong.

Visual intuition: Pass 1 is surveying land and planting numbered stakes (addresses) at every named lot (label). Pass 2 is the building crew that, now that every lot is staked, pours concrete at the staked positions using the right mold (opcode) for each lot.

13.10.2 Pass 1 — Assign Addresses and Save Label Values

Formalize — Pass 1's three jobs plus its I/O.

Jobs:

  1. Assign addresses: walk the source, maintain LOCCTR. Initialize operand of START (e.g., ). For each line, record its address, then advance LOCCTR by the size of that line (from OPTAB for instructions, from directive size for BYTE/WORD/RESB/RESW).
  2. Save label values: if a line has a label , insert current LOCCTR before advancing. This is the table Pass 2 will read.
  3. Partial directive handling: process directives that affect sizing — for BYTE reserve the constant's bytes, for WORD reserve one word, for RESB add bytes, for RESW add bytes, for START set the start, for END stop. No full object bytes need be finalized yet.

I/O:

  • Reads: source file + OPTAB (to know instruction lengths for LOCCTR bumps).
  • Writes: an intermediate file — each source line plus its assigned address, label, opcode/directive, operand, and any error flags — and the SYMTAB table.

Pass 1 has three main tasks. First, it assigns addresses to all statements in the program. That is done by stepping the location counter line by line. Second, it saves the values assigned to all labels for use in pass two, which is the collection into SYMTAB. Third, it does the early handling of assembler directives that affect sizing: for a directive such as BYTE it reserves the right number of bytes, for WORD it reserves a word, for RESB it reserves the indicated bytes, for RESW it reserves the indicated words, and for START it sets the beginning address.

The output of pass 1 is an intermediate file that records the per-line address that was assigned plus the label-to-value mapping. That intermediate file becomes the input to pass two. OPTAB and SYMTAB are the two tables that pass one consults and creates: OPTAB is read to know the size of each true machine instruction when bumping the location counter, and SYMTAB is written to remember every label and its address.

Trace — LOCCTR and SYMTAB after three Pass-1 lines.

Start: from COPY START 1000.

  • Line STL RETADR (no label, 3 bytes): address , . SYMTAB unchanged.
  • Line CLOOP JSUB RDREC (label CLOOP, 3 bytes): address , insert , .
  • Line LDA LENGTH (no label, 3 bytes): address , .

Later, BUFFER RESB 4096 (label BUFFER): address , , , so next label RDREC gets .

Result after END: SYMTAB holds every forward label too (EOF, THREE, ZERO, RETADR, LENGTH, BUFFER, RDREC, WRREC), and LOCCTR is at the program end — exactly what Pass 2 needs.

13.10.3 Pass 2 — Generate the Assembly and Finish the Program

Formalize — Pass 2's jobs.

Pass 2 walks the intermediate file (not the raw source), so it already knows each line's address.

  1. Assemble instructions: for each machine-instruction line, look up for opcode and for address, concatenate into opcode + address bytes.
  2. Generate data values: for BYTE/WORD lines, convert the constant operand to hex bytes (e.g., C'EOF'45 4F 46).
  3. Finish directive work: handle any assembler directive work not closed in pass one (validation, error reporting, entry point).
  4. Write outputs: emit the object program in H/T/E record form and the four-column assembly listing (location, label, instruction/directive, argument plus object bytes for instructions).

The phrasing used for pass 2 was "generate the assembly instructions, generate data values defined by BYTE and WORD, perform processing of the assembler directives not done in pass one, and write the object program and assembly listing."

Pass 2 walks the intermediate file. It takes the symbols that were collected and now gives them their numeric values inside the address fields. It assembles each instruction into its machine bytes, generates the data values for BYTE, WORD, and allied directives that need a value, finishes the handling of any assembler directive work that was not closed in pass one, and finally writes the object program in the H/T/E record form plus the four-column assembly listing that shows location, label, instruction or directive, and argument for each line.

Scope — why the intermediate file? The spec allows both passes to reread the raw source, but the intermediate file preserves Pass 1's LOCCTR and error flags per line so Pass 2 does not recompute or lose diagnostics. In the textbook (R7), Pass 1 writes source+address to intermediate file and Pass 2 reads that — the lecture follows that model. Skipping the intermediate file and rereading raw source would still work for addresses but would lose the error annotations that must travel to the listing.

13.10.4 Flow That Connects the Two Passes

The flow that was shown is: source program enters pass one, which consults OPTAB and SYMTAB while it creates an intermediate file. That intermediate file enters pass two, which generates values for the symbols, resolves the operand fields, and emits the object code.

In tabular form:

Pass Reads Writes Key table role
1 source + OPTAB intermediate file + SYMTAB (built) OPTAB gives instruction length to bump LOCCTR; SYMTAB records label addresses
2 intermediate file + OPTAB + SYMTAB object program (H/T/E) + listing OPTAB gives opcode byte; SYMTAB supplies address field

Each pass contributes one table role: OPTAB is static and holds the mnemonic-to-machine-value mapping for the processor, SYMTAB is built in pass one and consumed in pass two to replace symbolic names with numeric addresses.

Visual intuition: Pass 1 is a census taker who walks every street, writes each house number on a map (intermediate file) and logs "Mom lives at 1033" (SYMTAB). Pass 2 is the mail carrier who, map and address book in hand, delivers the correct stamped envelope to each house without ever knocking to ask "where do you live?"

13.10.5 Why Two Passes Are Enough Here

Because every forward reference becomes a known label by the end of the first sweep, the second sweep no longer needs to look ahead. Every address field can be filled immediately, including those that were blank the first time through. No third pass is needed under the lecture's assumptions: one definition per label, no nested forward-dependent expressions that would change LOCCTR after SYMTAB is built, and no relocation that would alter addresses after assembly. In richer assemblers with literal pools or forward-dependent EQU expressions, more passes or fixup tables may be needed — but for the SIC COPY program, two passes close every gap.

Recap + bridge. A pass is one full scan to END. Pass 1 assigns addresses via LOCCTR, saves every label to SYMTAB, and handles sizing directives, writing an intermediate file. Pass 2 reads that file, looks up opcodes in OPTAB and addresses in SYMTAB, emits data for BYTE/WORD, and writes the H/T/E object plus listing. Two passes suffice because the first makes every future label present. Next is the machinery that makes both passes fast and correct: the two tables and the counter that ties them together.

Real-world and domain placement: the "collect then fill" pattern appears far beyond assemblers — compilers collect declarations then emit code, linkers collect defined symbols then patch references, and even make collects dependencies then builds. Recognizing when a problem is "future information needed" tells you whether a second pass or a fixup table will be required, which is a routine design decision in systems tools.

13.11 Data Structures for Assembly — OPTAB, SYMTAB, and LOCCTR

13.11.1 OPTAB — The Operation Table

Hook — hundreds of mnemonics, one lookup. A real processor defines hundreds of operation codes. Scanning a list entry by entry for every source line would grind assembly to a crawl. How does the assembler find STL in microseconds?

OPTAB is the operation table. It is a static table. It holds every mnemonic code defined for the processor, taken from the instruction set architecture, and for each mnemonic it gives the machine language equivalent and, when the processor has a variable-length instruction format, the length of that instruction. A small view that was shown includes: STL whose machine equivalent is , LDA whose equivalent is , JSUB whose equivalent is , and analogs for COMP at , JEQ at , and J at . For a processor with variable-length instructions the entry also records the instruction length so the assembler knows how many bytes to advance.

The table is organized as a hash table with the mnemonic operation code as the key. The reason given is efficiency. Hundreds of mnemonics exist in an instruction set. Comparing each mnemonic in turn against every entry would be slow for every line — per lookup with in the hundreds, times thousands of source lines. Instead the assembler runs the mnemonic through the hash, gets the bucket that should hold the opcode, and picks the value from that slot in expected time. So on seeing "JSUB" it uses the hash to reach the bucket for JSUB and takes , and on seeing "LDA" it takes . That design is what makes the per-line translation of step 1 stay fast even when the table is large. Because the instruction set for a processor does not change while the source is being assembled, the table can be static for the entire run — built when the assembler itself is written, not rebuilt per program.

Formalize — OPTAB entry and hash.

  • Entry: . For COPY SIC view, length is uniformly 3 bytes; on SIC/XE it varies and format disambiguates.
  • Key: (string like "STL"). Hash → index in table → opcode. Textbook (R7) notes a general-purpose hash is normally sufficient; when OPTAB is static, a perfect hash or pre-tuned table length (often a prime) can give optimal performance, but most assemblers keep a standard hash.
  • Complexity: expected lookup versus linear scan — decisive when the source has thousands of lines and OPTAB has hundreds of entries.

Scope — static means per-assembler, not per-program. OPTAB is fixed for a given processor target (SIC, SIC/XE, x86, ARM each have their own OPTAB). Switching target means swapping the whole OPTAB, not editing entries per source file. That is why the assembler is machine-dependent even though its two-pass logic is not.

13.11.2 SYMTAB — The Symbol Table

SYMTAB is the symbol table, sometimes also called the symbol sync table in speech. It stores all the symbols, which are the various labels encountered in the program. Labels such as COPY, FIRST, CLOOP, ENDFIL, EOF, THREE, ZERO, RETADR, LENGTH, BUFFER, RDREC, and WRREC were listed as the kind of names that go there. Each entry holds a name and a value, where the value is the address where that label appears. Flags for error conditions are also kept alongside the name and value, in the sense used for the flag register that indicates overflow or a negative result; those flags can record conditions that arise during assembly (for example a symbol defined in two different places — duplicate label) and can be consulted when deciding how to handle a use of the symbol. Some implementations also keep type/length info for the labeled area.

The table is organized as a hash table as well, for the same efficiency reason. It helps both inserting a new label when it is first seen in pass one and retrieving the address for that label when it is needed as an operand in pass two. The per-line cost stays low because the hash brings the assembler directly to the neighborhood where the symbol is stored, rather than scanning the whole collection.

Formalize — SYMTAB operations.

  • Pass 1 insert: when a label is seen at current LOCCTR , do . If already exists, set duplicate-definition flag.
  • Pass 2 lookup: when operand is needed, do . If not found, set undefined-symbol flag and report error.
  • Hash key: the symbol name itself. Because programmers favor similar names (LOOP1, LOOP2, LOOPA or single-letter labels A,X,Y), the hash function must diffuse similar keys well. Textbook guidance (R7): divide the entire key's numeric value by a prime table length — that remainder distributes clusters well (a common textbook prime-mod hash).

Like OPTAB, expected per insertion/lookup; deletion is essentially never needed (symbols are not removed during assembly), so the hash need not optimize for deletion.

Worked SYMTAB snippet for COPY (after Pass 1).

Symbol Address Flags Meaning
COPY program start (from START)
CLOOP loop head in COPY
ENDFIL EOF handler
EOF "EOF" bytes (45 4F 46) location
THREE constant 3
ZERO constant 0
RETADR saved return address slot
LENGTH record length word
BUFFER start of 4096-byte reserved block
RDREC subroutine entry
WRREC subroutine entry

Pass 2's STL RETADR becomes 14 10 33 by reading 14 from OPTAB and 1033 from this SYMTAB — the two tables meeting in one instruction.

Pitfalls.

  • Calling SYMTAB "the label list." It is a hash table with flags, not a flat list — duplicate-definition detection lives in those flags, not in a separate pass.
  • Hashing only the first character. With many L-prefixed labels or single-letter symbols, a first-character hash collides badly. Hash the whole name.

13.11.3 LOCCTR — The Location Counter

Formalize — LOCCTR as the running address.

LOCCTR is the location counter. It points to the address of each instruction as assembly proceeds. It is initialized to the address specified by the START statement. In COPY that is from COPY START 1000, so at the start. After each source statement is processed, the length of the assembled instruction or the size of the data area that was generated is added to LOCCTR so it points to the next instruction. In symbols, where length is the instruction size from OPTAB for a true opcode or the byte or word count for a directive such as BYTE, WORD, RESB, or RESW. That stepping continues until END is reached. Because LOCCTR is updated after every line, it is the running record that lets pass one give every label its final address — the value of LOCCTR at the moment a label is seen is the SYMTAB value for that label.

Mathematical trace: starting from , a three-byte instruction advances it as , another three-byte instruction makes , and so on. When a reserved area is met, such as a BUFFER that occupies many bytes between the end of the main code near and the next routine at , the same rule applies: LOCCTR is advanced by the size that was reserved, which accounts for the jump in the address stream that is seen when reading the text records.

Trace — LOCCTR stepping through the boundary.

  • After COPY START 1000: .
  • After STL RETADR (3 bytes): → next line at 1003 (which is CLOOP).
  • After JSUB RDREC (3 bytes): .
  • After J CLOOP at : → ENDFIL lands at as table shows.
  • After data area totaling bytes ( EOF etc. through LENGTH, etc.): LOCCTR reaches near .
  • BUFFER RESB 4096: (since ). So the next code label RDREC is correctly placed at without any gap in the counting rule — the gap is the counted reservation.

Result: LOCCTR explains every address seen in SYMTAB and every start address in the Text records; any address mismatch on the exam traces back to a LOCCTR miscount.

Scope — whose LOCCTR? There is one LOCCTR per assembly. It is a simple integer variable inside the assembler, not a hardware register. On SIC/XE with control sections or program blocks, multiple location counters exist — but the lecture's simple assembler uses one, which is enough for COPY.

13.11.4 How the Three Work Together

In pass one, LOCCTR assigns the address, OPTAB tells how many bytes a true instruction needs so LOCCTR can be advanced by the right amount, and SYMTAB records the address that was just given to any label (label value = current LOCCTR). In pass two, SYMTAB supplies the address for every operand name, OPTAB again supplies the opcode byte, and LOCCTR is not the driver any longer because the addresses are already recorded; instead OPTAB and SYMTAB together convert each intermediate line into the final machine bytes that go to the T records. One variable (LOCCTR) creates the map in the first pass; two tables (OPTAB static, SYMTAB built) replay the map in the second.

Recap — who does what when.

  • Pass 1: steps, tells how far to step for opcodes, .
  • Pass 2: gives the high byte, gives the low two bytes, concatenated as the instruction.

Remembering that split — Pass 1 uses length, Pass 2 uses opcode; SYMTAB is written in Pass 1, read in Pass 2 — answers most "which structure in which pass?" exam questions.

13.11.5 Student Questions on the Tables

Q: Is the operation table the same for every program, and how is it searched quickly?

A: OPTAB is a static table for the processor. It lists the mnemonic-to-machine-value mapping for that instruction set, for example STL to , LDA to , JSUB to , COMP to , JEQ to , J to , and when needed the instruction length/format. It is organized as a hash table with the mnemonic as key. That lets the assembler find the opcode by hashing the mnemonic instead of comparing against every entry, which matters when the set contains hundreds of operation codes — expected versus linear. SYMTAB is the table that grows per program, holding label names with their assigned addresses and error flags, and it is also a hash table for fast insertion and retrieval. LOCCTR complements both: it is the running counter that is set by START to and then advanced by the per-line length ( for each SIC instruction, for each BYTE/RESB block). Together: LOCCTR numbers the lots, OPTAB knows the house style, SYMTAB remembers who lives where.

Recap + bridge. OPTAB is the per-processor static hash from mnemonic to opcode/length; SYMTAB is the per-program hash from label to address+flags built in Pass 1; LOCCTR is the integer that walks from START to END adding each line's size. Their interaction across two passes closes every forward reference and produces every hex byte in the H/T/E listing — the full two-pass machine in three structures.

Exam note: be ready to (a) walk LOCCTR with hex addition, (b) state which table is static versus built, (c) give the hash-key for each, and (d) use the trio to produce an object byte from a listing line. A frequent follow-on asks why a prime table length helps SYMTAB — answer: it spreads similar names like LOOP1/LOOP2 evenly.

Real-world and domain placement: these three structures survive in every modern assembler and compiler — LLVM's MCInstrInfo is OPTAB, its symbol table is SYMTAB, and the assembler's section offset is LOCCTR under another name. Debugging "duplicate symbol" or "invalid instruction" means consulting exactly the flags and lookups described here, and the hash-table reasoning is the same data-structures argument used for any high-frequency dictionary in systems code.

Exam Guidance Summary

The lecture included several points that map directly to how study and assessment are framed. This appendix collects them in one place so you can check each before the exam and before the assignment demo.

Exam note — the object-code question (assembler part). A question can give a sample program listing with locations, labels, instructions that mix true opcodes and assembler directives such as START, BYTE, WORD, RESB, RESW, and an END, with arguments naming symbolic operands (e.g., STL RETADR, JSUB RDREC). The task is to create the object code for that program. The route the examiner expects is the five assembler steps for real instructions — consult OPTAB for the opcode byte (e.g., , , ), replace each symbolic operand with the address found in SYMTAB (e.g., , ), build the three-byte instruction format, handle BYTE/WORD constants into internal hex/binary form, and write the H/T/E record listing with correct column fields and hex lengths. That question type was stated without a mark count but with clear expectation that it appears — practice the hex length conversion and the start-plus-length chaining until it is automatic.

What to rehearse for that question.

  • Hex length fluency: (ten 3-byte instructions), (seven instructions), , . Be able to convert both directions.
  • Address stepping: in hex, and the reason a jump like signals a RESB 4096 gap, not an error.
  • Directive nuance: RESB/RESW advance LOCCTR but emit no Text bytes; BYTE/WORD advance and emit. Mixing them changes every later start address.
  • Table discipline: OPTAB is per-processor and static; SYMTAB is per-program and built in Pass 1 — keep which table supplies which byte straight.

Scope — what is not in this round. Linker and loader are to be taken up in the next class. The next meeting was described as the last session on the 20th of the month, and it will carry the system calls that deal with process create, parent and child behavior, process exit, signals, and killing. Do not expect linker/loader record-relocation or process-create questions in this paper's assembler section — those belong to the next lecture.

Exam note — assignment and demo (shell programming). The assignment mentioned alongside the assembler notes has two parts, and each group has an assigned question-bank number — the mapping is the third column of the sheet shared earlier (group number → question-bank number in that folder). Two shell programs are needed, one for part one and one for part two; sub-functions may be used. They should make heavy use of the commands and special variables covered under shell programming last week, and comments should be added for clarity. The report should hold the code plus related details (objective, design, validation). The demonstration is set for the evening of the weekend after the next-week class, and both members of a group are expected to speak — both must be able to explain both parts; the format is not limited to one person presenting everything, and no PPT is required. Evaluation looks at: you show the shell program, state its objective, point out the places that address the requirement in the question, answer related questions, and show validation of the statements (test cases, edge handling).

Pitfalls for the demo.

  • One member preparing only one part. Both must explain both parts — the examiner will switch speakers.
  • Thin comments or no validation. The rubric rewards comments that map code to requirement and a demonstrated check (normal case, boundary, error case).
  • Treating the assembler object-code question and the shell assignment as unrelated. Both are systems programming — one shows translation, the other shows OS-level scripting — and the demo may ask how your script would be assembled/loaded if it were C instead of shell.

Practical tip from the lecture: for the object-code question, bring familiarity with hex counting for text-record lengths such as and , and with how the start address advances by adding each line's length. For the shell work, be ready to walk through the commands, the special variables (like \$?, \$#, \$*, \$@), and how errors or exit cases are handled — that is what the demo dialogue will probe.

Key Industry Applications

Why this lecture matters outside the exam hall. The assembler chain and the platform ideas from this lecture show up every time code moves from a laptop to a phone, from a build server to a device, or from a large install to a running process. Five concrete places were named.

  • Computer as hardware family (x86). Intel-based x86 parts — including 8086 and the numbered line through 586 Pentium, then dual core and Xeon — keep the same architecture label and share an instruction set under the x86 banner. Real-world: a Windows laptop and many servers present the same kind of native executable that runs only on that family. A build farm that compiles for x86 laptops must use an x86-targeting toolchain; cross-compiling to ARM without switching the target produces a file that will fault on the laptop.
  • ARM family in phones and small boards. An ARM-based phone, Android systems on ARM, and a board such as Raspberry Pi use a different instruction set with a different encoding. An x86 executable copied to ARM will not be recognized — the CPU raises an illegal-instruction fault — which is why the same C source must be translated again with an ARM-targeting compiler/assembler to produce a new binary. Real-world: mobile CI routinely builds two APK/ELF variants (x86 and ARM) from one source tree; embedded teams keep separate gcc-arm and gcc-x86 toolchains.
  • Java portability through a virtual machine. A Java class file contains bytecode (*.class), not a direct executable, and that bytecode travels to any machine that has a matching JVM. The JVM is part of the JRE and is specific to the hardware — a Linux x86 JVM, a Windows x86 JVM, and an ARM JVM are different binaries that each translate the same bytecode to their own instruction set on the fly at run time. Real-world: enterprise servers, Android apps, and Raspberry Pi Java programs can share the same .class distribution because the per-device JVM does the final translation; the cost is the JVM must be installed and the run-time translation step.
  • Partial program loading and demand paging. A large suite such as Microsoft Excel keeps many features in the same install, yet a rarely used facility such as macros may stay out of primary memory until the user starts it, at which point its code is loaded and then run. That demand-loading pattern — keep the working set resident, fault in the rest — lets the OS keep many programs alive on limited RAM and supports multitasking without forcing whole gigabyte programs to stay resident. By contrast small programs such as a few-line C program that adds two numbers, evaluates a quadratic, or walks a Fibonacci sequence are small enough to be loaded as a whole; demand handling would add overhead for no gain.
  • Systems programming as a practice. Shell programming and the use of the many commands and special variables covered earlier are systems-programming tasks that sit beside assembler and loader work. The tools named for the trade — assemblers, linkers, loaders, compilers, editors, and drivers — together take a source program through object program to executable and then to a placed image in memory. Real-world: a systems engineer writes a shell script to automate a build, an assembler patch to bring up a new board before the compiler is ready, a linker script to place firmware at a fixed flash address, and a loader tweak to support demand paging — all links of the same chain.

Takeaway — one chain, many jobs. Whether you are porting to ARM, shipping Java bytecode, sizing an Excel-like feature, or automating with shell, the diagnostic question is the same: which link of is responsible, and what ISA does it target? Answer that and the fix (rebuild, install JVM, tune residency, fix a symbol) is immediate.

Bridge to next lecture: the next session finishes the chain with linker/loader detail and the OS process model (create, parent/child, signals), turning the placed image you now understand into a running, scheduled process.

SP Lecture 13 notes · Assembler and System Software Fundamentals

Systems Programming· postgraduate· 2026-08-20

Sections Breakdown

1Software Categories -- System Software and Application Software

System software as the hardware-facing scaffold and application software as the outcome-facing tiles, with A=B+C showing why translation through the ISA is unavoidable.

2The Translation Chain -- From Source Program to Executable

Source to object via translator, object to executable via linker, executable to RAM via loader and scheduler, plus compiler versus assembler versus interpreter and demand loading.

3Hardware Platforms, Instruction Sets, and Why Executables Do Not Travel

ISA as a per-processor contract, x86 versus ARM incompatibility, why native executables are bound to one ISA and how Java bytecode travels via a hardware-specific JVM.

4Assembly Language -- Mnemonics, Symbolic Operands, and Instruction Format

Mnemonics as opcode nicknames fixed per ISA via OPTAB, symbolic operands via SYMTAB, three-byte SIC instruction format and conversion of constants to internal binary.

5Core Functions of the Assembler -- The Five Translation Steps

The ordered five jobs: mnemonic to bits, symbol to address, format concatenation, constant conversion, and emitting the H/T/E object program plus four-column listing.

6A Complete Assembly Example -- The COPY Program with RDREC and WRREC

COPY controller loop, RDREC indexed fill with X as pointer and eventual LENGTH, WRREC indexed drain, shared BUFFER and the 4096-byte reservation gap.

7Assembler Directives -- Pseudo-Instructions that Guide the Assembler

START and END framing, BYTE and WORD emitting versus RESB and RESW reserving, and how each advances LOCCTR and shapes Text records.

8The Object Program -- Header, Text, and End Records

H/T/E record column contracts, hex length arithmetic 1E=30 and 15=21, address chaining start+length and the BUFFER gap skipping.

9Forward References and the Need for Two Passes

Forward versus backward references in COPY, why step 2 needs future labels and how that single fact forces a second pass.

10The Two-Pass Assembler -- What Pass 1 and Pass 2 Do

Pass 1 collecting addresses and building SYMTAB into an intermediate file, Pass 2 using OPTAB and SYMTAB to emit object bytes, and why two passes close every gap here.

11Data Structures for Assembly -- OPTAB, SYMTAB, and LOCCTR

Static OPTAB hash for opcode lookup, per-program SYMTAB hash with flags, LOCCTR stepping from START, and their coordinated roles.

12Exam Guidance Summary

Appendix consolidating the object-code question pattern, directive handling, hex fluency, and the shell assignment demo expectations.

13Key Industry Applications

Appendix mapping assembler concepts to x86 and ARM toolchains, bytecode portability, demand paging in large installs like Excel, and daily systems programming practice.

Postgraduate students in Systems Programming

Exam Revision Notes

Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.

Software Categories -- System Software and Application Software

Must-know: Application needs system software which needs hardware; A=B+C becomes move/add/store.

Top pitfall: Calling a driver not software or thinking CPU runs C text directly.

Self-check: Classify Gmail vs OS scheduler -- which is system vs application?

Connects to: 13.2

The Translation Chain -- From Source Program to Executable

Must-know: Chain S->O->E->RAM->CPU; compiler=whole program, interpreter=line by line, assembler=asm->bits; Excel macros demand-loaded.

Top pitfall: Calling object the executable or confusing linker with loader.

Self-check: Order the four stages and name which fixes unresolved externals.

Connects to: 13.1, 13.3

Hardware Platforms, Instruction Sets, and Why Executables Do Not Travel

Must-know: Native executable bound to its ISA; Java bytecode portable because hardware-specific JVM translates on the fly.

Top pitfall: Thinking executable means universal; confusing file copy with ISA re-encoding.

Self-check: Why does copying sum.exe from x86 to ARM fault, but copying app.class under JVM succeeds?

Connects to: 13.2, 13.4

Assembly Language -- Mnemonics, Symbolic Operands, and Instruction Format

Must-know: Mnemonic->opcode via OPTAB (STL=14 etc.), symbol->address via SYMTAB, format opcode+address, constants to bits.

Top pitfall: Treating mnemonic as whole instruction; mixing decimal and hex addresses.

Self-check: Build object bytes for STL RETADR given OPTAB 14 and SYMTAB 1033.

Connects to: 13.5

Core Functions of the Assembler -- The Five Translation Steps

Must-know: Five steps in order; step 2 needs SYMTAB and is hard because of forward refs; STL RETADR=14 1033.

Top pitfall: Treating START/BYTE as opcodes; expecting address without SYMTAB.

Self-check: Which of five steps cannot be done in one scan and why?

Connects to: 13.6, 13.9

A Complete Assembly Example -- The COPY Program with RDREC and WRREC

Must-know: COPY loop JSUB RDREC->LDA LENGTH->COMP ZERO->JEQ/JSUB WRREC->J CLOOP; RDREC fills, WRREC drains, X dual role.

Top pitfall: Off-by-one X<=LENGTH; forgetting to clear X.

Self-check: Trace RDREC reading Hi with MAXLEN 10 -- what are BUFFER and LENGTH?

Connects to: 13.5, 13.7

Assembler Directives -- Pseudo-Instructions that Guide the Assembler

Must-know: Directives are pseudo-instructions; START sets LOCCTR, BYTE/WORD emit, RESB/RESW only reserve.

Top pitfall: Expecting RESB to emit Text bytes; forgetting directives still bump LOCCTR.

Self-check: Why does RESB 4096 cause next Text start to jump from 1039 to 2039?

Connects to: 13.8, 13.11

The Object Program -- Header, Text, and End Records

Must-know: H cols 1/2-7/8-13/14-19; T cols 1/2-7/8-9/10-69; max 30 bytes=10 instr; E 001000.

Top pitfall: Reading length 1E as 30 instr not bytes; miscounting 2-7 as 5 not 6.

Self-check: Next start = current start + length: 002039+1E = ?

Connects to: 13.7, 13.9

Forward References and the Need for Two Passes

Must-know: Five forwards in COPY (RETADR,RDREC,LENGTH,ZERO,ENDFIL) vs backward J CLOOP; step2 hard so two passes.

Top pitfall: Thinking one-line lookahead fixes forward; confusing undefined with forward.

Self-check: Label RETADR used at 1000 defined at 1033 -- forward or backward?

Connects to: 13.10

The Two-Pass Assembler -- What Pass 1 and Pass 2 Do

Must-know: Pass1 reads source+OPTAB writes intermediate+SYMTAB; Pass2 reads intermediate+OPTAB+SYMTAB writes HTE+listing.

Top pitfall: Rereading raw source and losing error flags; calling OPTAB per-program.

Self-check: Which table is written in Pass1 and read in Pass2?

Connects to: 13.9, 13.11

Data Structures for Assembly -- OPTAB, SYMTAB, and LOCCTR

Must-know: OPTAB static hash O(1), SYMTAB per-program hash with flags, LOCCTR=1000 start + length per line.

Top pitfall: Hashing only first char; forgetting SYMTAB flags for duplicate/undefined.

Self-check: LOCCTR at 1000 + 3-byte instruction -> next LOCCTR?

Connects to: 13.10

Exam Guidance Summary

Must-know: Object-code question uses five steps+directives->HTE; next lecture is linker/loader+process syscalls; demo needs both members explaining both shell parts.

Top pitfall: Studying linker detail for this paper; preparing only one shell part.

Self-check: What two shell parts must both members be able to explain?

Connects to: 13.8, 13.5

Key Industry Applications

Must-know: x86 vs ARM rebuild, Java flat-pack+JVM, Excel macros demand-loaded, small C programs fully loaded, shell as systems practice.

Top pitfall: Treating executable as portable; assuming macros always resident.

Self-check: Name one case where demand loading wins over full load.

Connects to: 13.3, 13.2

Was this lecture useful?

Loading comments…
🤖

BitsNotes AI Assistant

Subject Notes Assistant

Configure AI Chat

Choose how to access the chatbot
Have your own API key?

Switch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.

🔑 Enter API key above to fetch live models from provider, or enter model name manually.
OpenAI-Compatible API Support

Choose any provider preset (Gemini, DeepSeek, Kimi, GLM, MiniMax, Qwen, OpenAI, Groq, Ollama, etc.) or enter a custom endpoint URL.

Security & Privacy First

Your API key is sent directly from your browser to your specified provider. BitsNotes servers never store or see your key.