A Beginner 8086 Assembly Lab: Copying Program Code

A small code-installation exercise

Posted by Bruce Lee on 2023-07-15

About Me

Welcome to my blog! This is where I collect my observations and notes on programming and technology. The main subjects range from implementation details to broader ideas about programming.

Main Topics

  • Engineering Projects: Exploring implementation details and how technical systems work.
  • C/C++: Notes on language features and programming techniques.
  • The Programmer’s Perspective: Ideas about developing a career and a way of thinking as a programmer.

For more, visit the categories page.

Contact

If you have questions or would like to discuss something, please get in touch through the About page.

Thank you for reading and for your support. I hope these notes help you on your own technical journey!


The exercise

This small experiment prepares for a later interrupt-handler installation exercise. Write a program that copies its own machine code, stopping before mov ax,4c00h, into another memory region. Assume the program begins at CS:0000.

The source contains two different destination notations: 0200:0000 in the question and 0020:0000 in the code. They are different physical addresses. The walkthrough below preserves the code’s 0020:0000, physical address 0x200, which lies inside the 8086 interrupt vector table. This is a historical DOS/emulator exercise and overwrites vector-table storage; it is not an installation location to use indiscriminately in a running system.

The first 1 KiB of real-mode memory holds interrupt vectors. Interrupts transfer control through that mechanism; an arbitrary jump elsewhere is not, by itself, an interrupt.

Here is the incomplete program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code
code segment
mov
mov
mov ax,0020h
mov es,ax
mov bx,0
mov cx,
s: mov al,ds:[bx]
mov es:[bx],al
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end

Try to fill the blanks before reading the explanation. The central questions are the source address, destination address, and number of bytes.

Identify the source

The code segment is identified by CS. We want DS to refer to the same segment so that ordinary data loads can read the program bytes. The exercise explicitly fixes the starting offset at zero.

The original notes mention 0100h as another familiar entry offset. That is characteristic of DOS .COM programs; an .EXE entry point follows its executable header and loading arrangement. We should use the stated assumption rather than infer an offset from habit.

The first attempted instruction is:

1
mov ds,cs

Its intended meaning is clear, but the encoding is invalid: 8086 MOV does not directly copy one segment register into another. We will correct it after seeing the assembler diagnostic.

Identify the destination

1
2
3
mov ax,0020h
mov es,ax
mov bx,0

ES names the destination segment, and BX starts at offset zero for both source and destination. The loop loads a byte through DS:BX, stores it through ES:BX, increments the offset, and repeats.

Determine the loop count

1
2
3
4
s:  mov al,ds:[bx]
mov es:[bx],al
inc bx
loop s

LOOP decrements CX and branches if the resulting count is nonzero. The reference to decrementing CS in the original narrative is a typo. For a positive initial count of five, the body executes five times; for eight, eight times. Starting at zero would wrap the 16-bit count and is not a zero-iteration shortcut.

We need the number of bytes from the start through the instruction immediately before mov ax,4c00h. Instruction lengths vary with opcode and operand encoding, so manually adding them is possible but tedious.

For this beginner experiment, put a provisional immediate into CX, assemble and link, and use DEBUG’s u command to find the actual end offset. A 16-bit immediate in this particular mov cx,imm16 form keeps the instruction length unchanged when the number changes. Later, labels and an expression such as offset copy_end - offset copy_start provide a cleaner solution.

The first build

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment
mov ds,cs
mov ax,0020h
mov es,ax
mov bx,0
mov cx,0010h
s: mov al,ds:[bx]
mov es:[bx],al
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end

Running masm code.asm; produces:

1
code.asm(4): error A2019: Wrong type of register

The failing line is mov ds,cs. Both operands are segment registers, and no such MOV encoding exists. The original explanation attributes this to segmentation security; the direct reason in this exercise is the instruction’s allowed operand forms.

Use a general register as an intermediate:

1
2
mov ax,cs
mov ds,ax

This explains why the skeleton reserved two instructions.

Run masm code.asm;, followed by link code.obj;. The assembler creates the object file; the linker creates code.exe. Running this program does not print anything because its useful work consists of memory writes.

Start debug code.exe, then use r to inspect registers. One recorded run began with:

1
2
AX=FFFF BX=0000 CX=001C DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=075A ES=075A SS=0769 CS=076A IP=0000 NU UP EI PL NZ NA PD NC

Addresses vary between sessions. Use u cs:0000 to disassemble the program. The relevant offsets are:

1
2
3
4
5
6
7
8
9
10
11
12
0000  MOV AX,CS
0002 MOV DS,AX
0004 MOV AX,0020
0007 MOV ES,AX
0009 MOV BX,0000
000C MOV CX,0010
000F MOV AL,[BX]
0011 ES: MOV [BX],AL
0014 INC BX
0015 LOOP 000F
0017 MOV AX,4C00
001A INT 21

The original dump includes transcription mistakes in an opcode and a repeated MOV; the instruction offsets are what matter here. DEBUG may display a segment prefix separately from its following instruction.

Fix the length

The bytes before offset 0017h occupy the half-open range [0000h, 0017h), so their count is 0017h, or 23 bytes. The source’s proposed 0018h is an off-by-one error and would copy the first byte of the termination instruction too. Set mov cx,0017h for this exact assembled layout, then rebuild and verify the destination bytes.

MASM hexadecimal literals conventionally use the h suffix. DEBUG’s disassembly displays hexadecimal values without requiring the same suffix. At the final DOS interrupt, use DEBUG’s proceed command when you want to step over the interrupt rather than trace into its handler.


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !