UD80(1)

NAME
ud80 - 8080/Z80 disassembler for CP/M .COM files

SYNOPSIS
ud80 [-v] [-z] [-o output.mac] [-e address[,label]]... [-l address[,label]]... [-d start-end]... [-t start-end]... [-dc start-end]... [-da start-end[,length]]... [--org address] input.com

DESCRIPTION
ud80 is a disassembler for 8080 and Z80 binary files, designed to produce output compatible with the um80(1) assembler. It converts CP/M .COM executable files (or raw binary files) back into assembly source code that can be modified and reassembled.

The disassembler performs flow analysis starting from entry points to distinguish code from data. Unvisited bytes are output as DB directives. The output is designed to reassemble to an identical binary with um80.

OPTIONS

-v, --version
    Display version information and exit.

-z, --z80
    Enable Z80 mode. In this mode:

    •   Z80 extended instructions are decoded (CB, DD, ED, FD prefixes)

    •   Undocumented Z80 instructions are recognized

    •   Zilog mnemonics are used in the output

    Without this flag, only 8080 instructions are decoded using Intel mnemonics.

-o output.mac
    Specify the output file name. Default is the input file name with a .mac extension.

-e address[,label]
    Add an entry point at the specified hexadecimal address. Flow analysis will trace execution from this address. Can be specified multiple times.

    Optionally, specify a label name after a comma (e.g., -e 297a,ends). The label will be used in the output instead of an auto-generated one.

    By default, the disassembler starts at the origin address (typically 0100h for CP/M programs). Use -e to add additional entry points for code that isn't reached by normal flow (interrupt handlers, jump tables, etc.).

-l address[,label]
    Add a label at the specified address without tracing it as code. Can be specified multiple times.

    Optionally, specify a label name after a comma (e.g., -l 4406,buffer). If no label name is given, an auto-generated label (Lxxxx) is used.

    Use this for data labels or for code that you want to label but not trace (e.g., when you know it's already reached by other means).

-d start-end
    Force a range of addresses to be treated as data, not code. Both start and end are hexadecimal addresses. The range is inclusive. Can be specified multiple times.

    Use this for embedded data tables that might otherwise be incorrectly disassembled as instructions.

-t start-end
    Mark a range of addresses as an address table. The range will be output as DW (define word) directives with labels where possible, instead of individual DB bytes. Both start and end are hexadecimal addresses. Can be specified multiple times.

    Use this for jump tables or dispatch tables containing 16-bit addresses. The table entries will be output as:
    DW      L1234
    DW      L5678
    ...

-dc start-end
    Mark a range as containing high-bit terminated strings. These are strings where the last character has bit 7 set (OR 80H). Common in keyword tables. Both start and end are hexadecimal addresses. Can be specified multiple times.

    The output uses the DC directive which sets the high bit on the last character:
    DC      'ABC'

    Note: Each -dc marks one string region. Token bytes or other data between strings should not be included in the range.

-da start-end[,length]
    Mark a range as containing ASCII strings. Without a length parameter, strings end at a null byte (00H) or any non-printable character. With a length, strings are fixed-length. Both start and end are hexadecimal addresses. Can be specified multiple times.

    Examples:
    -da 0200-02FF          # Strings end at null or non-printable
    -da 0200-02FF,8        # Fixed 8-character strings

    Output format: DB 'HELLO'
    Any null terminator is output as a separate DB 00H directive.

--org address
    Set the origin address (where the binary is loaded in memory). Default is 0100h, the standard CP/M transient program area. The address is in hexadecimal.

FLOW ANALYSIS
ud80 performs recursive descent disassembly starting from entry points:

1.  Begin at each entry point

2.  Decode instructions, following sequential execution

3.  When encountering branches (JMP, JNZ, CALL, etc.), queue branch targets

4.  Stop tracing at unconditional jumps, returns, and halts

5.  Continue until all reachable code is traced

Post-jump gap detection: After an unconditional jump (JMP, RET, HLT), the bytes immediately following are typically data, not code. The disassembler automatically detects these "post-jump gaps" and marks them as data, preventing false code traces that might otherwise occur if stale addresses happen to point into these regions.

Automatic jump table detection: The disassembler automatically detects address tables (jump/dispatch tables) using two methods:

•   Post-entry tables: After the initial JMP at an entry point, if the following bytes form a sequence of valid addresses (3 or more consecutive 16-bit values pointing within the code range), they are treated as an address table.

•   Dispatch pattern detection: Code sequences like "LXI H,addr / DAD B" indicate a table lookup. The disassembler recognizes this pattern and marks the referenced address as a table base.

Automatically detected tables are output as DW directives with labels, making the disassembly more readable.

This approach correctly identifies code vs. data in most cases, but may miss code only reached through computed jumps or interrupt vectors.

OUTPUT FORMAT
The output is a valid um80(1) source file:

    ; Disassembly of program.com
    ; Generated by ud80
            ORG     0100H
    L0100:  JMP     L0150       ; Jump to main
    L0103:  DB      0,0,0       ; Data bytes
            ...
    L0150:  MVI     A,0         ; Main entry
            ...
            END

Features of the output:

•   Labels are generated for all branch targets (Lxxxx format)

•   Unreachable bytes are output as DB directives

•   String-like data is shown as DB with ASCII strings where possible

Note: The END directive is output without an entry point label. In CP/M, execution always begins at the load address (typically 0100H), so the entry point is implicit. The first linked module determines where execution starts. An END with an explicit label would only be useful for non-CP/M targets (such as ROM-based systems) where a startup JMP would be inserted at the load address.

8080 vs Z80 MODE

  8080 Mode (default)
Uses Intel 8080 mnemonics:

•   MOV, MVI, LXI for data movement

•   ADD, ADC, SUB, SBB for arithmetic

•   ANA, XRA, ORA for logic

•   JMP, JNZ, JZ, CALL, RET for control flow

•   Registers: A, B, C, D, E, H, L, M, SP, PSW

  Z80 Mode (-z)
Uses Zilog Z80 mnemonics:

•   LD for most data movement

•   ADD, ADC, SUB, SBC for arithmetic

•   AND, XOR, OR for logic

•   JP, JR, CALL, RET, DJNZ for control flow

•   Additional registers: IX, IY, I, R

•   Extended instructions: LDIR, CPIR, bit operations, etc.

Z80 mode also decodes the CB, DD, ED, and FD prefix instructions, including undocumented opcodes for the IXH, IXL, IYH, IYL registers.

EXAMPLES

Basic disassembly:
    ud80 program.com

Z80 mode:
    ud80 -z program.com

Custom output file:
    ud80 -o source.mac program.com

Add entry point:
    ud80 -e 0200 program.com

Multiple entry points:
    ud80 -e 0200 -e 0300 -e 0400 program.com

Force data range:
    ud80 -d 0500-05FF program.com

Mark jump table (output as DW):
    ud80 -t 0103-0120 program.com

Non-CP/M binary at 0000h:
    ud80 --org 0000 bios.bin

Combine options:
    ud80 -z -e 0200 -d 0300-03FF -t 0103-0120 -o source.mac game.com

ROUND-TRIP VERIFICATION
To verify disassembly quality, reassemble and compare:

    ud80 program.com
    um80 program.mac
    ul80 program.rel
    cmp program.com program.new

If the files match, the disassembly is accurate.

TIPS

  Finding Entry Points

•   Look for interrupt vectors at fixed addresses (RST handlers, etc.)

•   Search for jump table patterns

•   Examine call targets that flow analysis missed

  Identifying Data

•   Strings of ASCII characters

•   Tables of addresses (pairs of bytes)

•   Sequences that don't decode as valid instructions

•   Areas following unconditional jumps that aren't targeted by branches

  Iterative Refinement

1.  Run initial disassembly

2.  Examine output for incorrectly decoded data

3.  Add -d flags for data regions

4.  Add -e flags for missed code

5.  Re-disassemble and verify

EXIT STATUS

0   Disassembly successful.

1   Error occurred (file not found, invalid address, etc.).

FILES

input.com
    Input binary file (CP/M executable or raw binary).

output.mac
    Output assembly source file.

LIMITATIONS

•   Self-modifying code cannot be correctly disassembled.

•   Computed jumps (JP (HL), etc.) don't contribute to flow analysis.

•   Overlay areas or banked memory cannot be handled automatically.

•   CP/M system calls are not annotated (would require additional analysis).

SEE ALSO
um80(1), ul80(1), ulib80(1), ucref80(1)

HISTORY
ud80 was created as a companion tool for the um80 assembler toolchain. Unlike the other tools which are implementations of Microsoft originals, ud80 is a new tool designed to complement the toolchain by enabling disassembly of existing CP/M programs.

AUTHOR
um80 project contributors.