فن
خوبصورتی اور تندرستی
دستکاری
ثقافت اور تاریخ
تفریح
ماحول
خوراک اور مشروبات
ریورس انجینئرنگ
سائنسز
کھیل
ٹیکنالوجی
پہننے والے آلات
Machine Code and the Assembler
Pixel

تخلیق کار

Pixel

27. اگست 2026FI
1
0
0
0
0

Machine Code and the Assembler

Once instructions are numbers in memory, somebody has to write those numbers. The first programmers wrote them literally — in binary or octal, on paper, tracking every memory address by hand, and re-computing every address whenever an instruction was inserted. It was reliable in the sense that the machine did exactly what the numbers said, and unreliable in every human sense. The assembler is the first program written to help write programs: it lets you type ADD instead of 3, and a label instead of an address, and does the clerical work of turning those into numbers. That sounds trivial and it was resisted, because machine time was precious and using the computer to prepare its own programs looked like waste. It is also the moment software began building on itself.
درمیانہ
5 hours 30 minutes

ہدایات

1

Hand-assemble a program and then insert one line

Do it the 1949 way once. The lesson arrives in step 4 of the exercise, not step 1.

  1. Take the countdown program from the stored-program blueprint and write it out on paper as a table: address, instruction, meaning.
  2. Convert each mnemonic to its opcode number by hand and write the final memory image.
  3. Check it by walking the program on paper.
  4. Now INSERT one extra instruction near the top — and fix everything that breaks.

Every jump target after the insertion point is now wrong, and every data address has moved. One added line means recomputing the whole program by hand, and a single missed jump gives a machine that runs and produces nonsense.

This is the actual daily experience of early programming, and it is why the first tools were not compilers or debuggers but simply things that tracked addresses for you. The bottleneck was never the thinking — it was the clerical work, and clerical work is exactly what machines are good at.

اس مرحلے کے لیے مواد:

Graph PaperGraph Paper1 pad

درکار اوزار:

Desktop ComputerDesktop Computer
2

Write a two-pass assembler

Run it, then do the experiment that matters: insert an extra instruction near the top and run it again. Every address and every jump target is recomputed automatically — the thing that cost you an hour in step 1.

The comment block explains why it needs two passes: a forward jump refers to a label that has not been defined yet, so pass 1 assigns addresses and collects labels, and pass 2 emits code once every name is known.

That unresolved-forward-reference problem never really goes away. When an assembler cannot see the target at all — because it is in a different file — it emits a placeholder and a note saying “fill this in later”. Something must then do the filling in, and that something is the linker. Linkers exist because of exactly the problem this assembler solves within one file.
assembler.pypython
#!/usr/bin/env python3
"""
A two-pass assembler for the machine in the Stored-Program Computer blueprint.
Youblob blueprint: Machine Code and the Assembler

Instruction word = opcode*100 + operand.
  0 HLT   1 LOAD  2 STORE  3 ADD  4 SUB  5 JMP  6 JZ  7 OUT

WHY TWO PASSES: a program can jump FORWARD to a label that has not been seen yet.
Pass 1 walks the source assigning addresses and recording where every label lands.
Pass 2 emits code, by which point every label is known. A one-pass assembler must
either forbid forward jumps or patch them up afterwards -- which is exactly what a
linker does, and why the job exists at all.
"""

OPCODES = {"HLT":0, "LOAD":1, "STORE":2, "ADD":3, "SUB":4, "JMP":5, "JZ":6, "OUT":7}


def parse(source):
    """Strip comments and blanks, split each line into (label, mnemonic, operand)."""
    out = []
    for lineno, raw in enumerate(source.splitlines(), 1):
        line = raw.split("#")[0].strip()
        if not line:
            continue
        label = None
        if ":" in line:
            label, _, line = line.partition(":")
            label = label.strip()
            line = line.strip()
        parts = line.split()
        mnem = parts[0].upper() if parts else None
        operand = parts[1] if len(parts) > 1 else None
        out.append((lineno, label, mnem, operand))
    return out


def assemble(source):
    parsed = parse(source)

    # ---- PASS 1: assign addresses, collect labels -------------------------
    symbols, addr = {}, 0
    for lineno, label, mnem, operand in parsed:
        if label:
            if label in symbols:
                raise SyntaxError(f"line {lineno}: label '{label}' defined twice")
            symbols[label] = addr
        if mnem:                       # DATA reserves a word, like any instruction
            addr += 1

    # ---- PASS 2: emit machine code ----------------------------------------
    image = []
    for lineno, label, mnem, operand in parsed:
        if mnem is None:
            continue
        if mnem == "DATA":
            image.append(int(operand or 0))
            continue
        if mnem not in OPCODES:
            raise SyntaxError(f"line {lineno}: unknown mnemonic '{mnem}'")
        op = OPCODES[mnem]
        if operand is None:
            target = 0
        elif operand.isdigit():
            target = int(operand)
        elif operand in symbols:
            target = symbols[operand]      # THE WHOLE POINT: name -> address
        else:
            raise SyntaxError(f"line {lineno}: undefined label '{operand}'")
        image.append(op * 100 + target)

    return image, symbols


PROGRAM = """
# count down from N, printing each value
start:  LOAD  count
loop:   OUT
        SUB   one
        STORE count
        JZ    done          # forward reference -- impossible in one pass
        JMP   loop
done:   HLT
count:  DATA  5
one:    DATA  1
"""

if __name__ == "__main__":
    image, symbols = assemble(PROGRAM)
    print("symbol table:", symbols)
    print("\naddr  word   meaning")
    names = {v: k for k, v in OPCODES.items()}
    for a, w in enumerate(image):
        print(f"{a:>4}  {w:>4}   {names.get(w // 100, 'DATA'):<6}{w % 100}")
    print("\nInsert a line anywhere above and re-run: every address is recomputed")
    print("for you. That is the entire value of the assembler.")

درکار اوزار:

Desktop ComputerDesktop Computer
Computer with Arduino IDEComputer with Arduino IDE
3

Assemble, load, run

Follow the pipeline. The names exist only in the source and in the assembler's symbol table — the machine never sees them. They are entirely for the human, and they cost nothing at run time.

Notice where the errors are caught. Pass 1 catches duplicate labels, pass 2 catches undefined ones, and both fail BEFORE anything is loaded. The assembler is the first program that checks your work, and refusing to produce output is the useful behaviour.

Keeping the symbol table around instead of discarding it is what makes a debugger possible: it is the only thing that can map an address back to the name you wrote. That is precisely what a “debug build” and a stripped binary differ by, and why a crash in a stripped binary gives you hex addresses instead of function names.

Flow

Loading...

درکار اوزار:

Desktop ComputerDesktop Computer
4

Measure what the abstraction costs and buys

Loading Jupyter Notebook...

درکار اوزار:

Desktop ComputerDesktop Computer
5

Bootstrapping: the program that builds itself

The assembler is written in something. Follow that back and you meet a genuine chicken-and-egg problem.

  1. Your assembler is written in Python, which is itself a program, which was compiled by a C compiler, which is written in C.
  2. Ask what compiled the first C compiler.
  3. Now consider writing an assembler FOR your machine, IN the assembly language of your machine.

The chain terminates in something written by hand in raw machine code. Someone hand-assembled the first assembler; that assembler then assembled a better one; and every tool since has been built with the tools before it.

Once a language can express its own translator, you can write version two in version one, assemble it with version one, and from then on the language builds itself. That is bootstrapping, and it is why compilers for a language are usually written in that language.

Ken Thompson's 1984 lecture “Reflections on Trusting Trust” takes this somewhere uncomfortable: a compiler can be taught to insert a backdoor into a program AND into any future compiler it compiles, then have that instruction removed from its own source. The bug persists in every descendant with nothing visible in any source file. It is the deepest consequence of the stored-program idea — if code is data, then code can be made to lie about code — and it is unfixable by reading source alone.

درکار اوزار:

Desktop ComputerDesktop Computer

مواد

1

درکار اوزار

2

CC0 پبلک ڈومین

یہ بلیو پرنٹ CC0 کے تحت جاری کیا گیا ہے۔ آپ اجازت لیے بغیر اس کام کو نقل، ترمیم، تقسیم اور کسی بھی مقصد کے لیے استعمال کرنے کے لیے آزاد ہیں۔

میکر کی حمایت کریں ان کے بلیو پرنٹ کے ذریعے پروڈکٹس خرید کر جہاں وہ میکر کمیشن وینڈرز کی طرف سے مقرر، کماتے ہیں، یا اس بلیو پرنٹ کی نئی تکرار بنائیں اور آمدنی شیئر کرنے کے لیے اسے اپنے بلیو پرنٹ میں کنکشن کے طور پر شامل کریں۔

بحث

(0)

لاگ ان بحث میں شامل ہونے کے لیے

تبصرے لوڈ ہو رہے ہیں...