view main.c @ 16:a2a81589380d default tip

Reformat the whole source via clang-format for better consistency.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 14 Oct 2021 01:53:20 +0300
parents 89183953bddc
children
line wrap: on
line source

/*\
 *  dxa -- symbolic 65xx disassembler
 *
 *  Based on d65 Copyright (C) 1993, 1994 Marko M\"akel\"a
 *  Changes for dxa (C) 2005-2019 Cameron Kaiser
 *  Modifications for ++ version (c) 2015-2021 Matti 'ccr' Hamalainen <ccr@tnsp.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  Marko does not maintain dxa, so questions specific to dxa should be
 *  sent to me at ckaiser@floodgap.com.
 *
\*/

#define _MAIN_C_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __GNUC__
#include <unistd.h>
#endif
#ifdef LONG_OPTIONS
#include <getopt.h>
#endif /* __GNUC__ */
#include "opcodes.h"
#include "options.h"
#include "proto.h"


int main(int argc, char **argv)
{
    FILE *file;
    unsigned address1, address2;
#ifdef LONG_OPTIONS
    int option_index;
#endif
    int optsfinished = FALSE, ftype;
    char labelname[MAXLINE], strig[MAXLINE], *scanner;

    extern char *optarg;
    extern int optind;

#ifdef LONG_OPTIONS
    static struct option cmd_options [] = {
        { "datablock", 1, 0, 'b' }, /* an address range to be marked
                                       as a data block */
        { "datablocks", 1, 0, 'B' },/* a file containing the address ranges
                                       to be marked as data blocks */
        { "labels", 1, 0, 'l' },    /* a file containing labels to be translated
                                       in the output phase */
        { "routine", 1, 0, 'r' },   /* an address of a routine */
        { "routines", 1, 0, 'R' },  /* a file containing the addresses */

        { "listing-width", 1, 0, 'L' },
                                    /* maximum number of dumped bytes per line */
        { "addresses", 1, 0, 'a' }, /* dumping addresses in the output phase */
        { "datablock-detection", 1, 0, 'd' },
                                    /* data block detection options */
        { "processor", 1, 0, 'p' }, /* instruction set */
        { "no-colon-newline", 0, 0, 'n' },
        { "colon-newline", 0, 0, 'N' },
        { "no-external-labels", 0, 0, 'e' },
        { "external-labels", 0, 0, 'E' },
        { "address-tables", 0, 0, 't' },
        { "no-address-statements", 0, 0, 's' },
        { "address-statements", 0, 0, 'S' },
        { "suspect-jsr", 0, 0, 'J' },
        { "no-suspect-jsr", 0, 0, 'j' },
        { "one-byte-routines", 0, 0, 'O' },
        { "no-one-byte-routines", 0, 0, 'o' },
        { "stupid-jumps", 0, 0, 'M' },
        { "no-stupid-jumps", 0, 0, 'm' },
        { "allow-brk", 0, 0, 'W' },
        { "no-allow-brk", 0, 0, 'w' },
        { "suspect-branches", 0, 0, 'C' },
        { "no-suspect-branches", 0, 0, 'c' },
        { "cross-reference", 0, 0, 'X' },
        { "no-cross-reference", 0, 0, 'x' },
        { "verbose", 0, 0, 'v' },
        { "help", 0, 0, '?' },
        { "word-sa", 0, 0, 'Q' },
        { "no-word-sa", 0, 0, 'q' },
        { "get-sa", 0, 0, 'G' },
        { "no-get-sa", 0, 0, 'g' },
        { "detect-basic", 0, 0, 'U' },
        { "no-detect-basic", 0, 0, 'u' },
        { NULL, 0, 0, 0 }
    };
#endif /* LONG_OPTIONS */

    opset = standard_nmos6502;

    StartAddress = 0;
    Options = O_ADR_ADR_DMP | B_LBL_ALWAYS | O_TBL_NOEXT | B_STM_DETECT | B_STK_BALANCE | B_RSC_STRICT | O_DBL_STRICT |
              B_JMP_STRICT | B_BRK_REJECT;

    /* dxa defaults */
    Options = (Options & ~M_ADDRESSES) | O_ADR_NOTHING;
    Options = (Options & ~M_DATA_BLOCKS) | O_DBL_IGNORE;
    Options &= ~B_LBL_ALWAYS;
    Options &= ~B_LABCOL;
    Options |= B_SA_WORD;
    Options |= B_GET_SA;

    for (address1 = sizeof MemType / sizeof *MemType; address1--; MemType[address1] = 0);

    for (address1 = sizeof MemFlag / sizeof *MemFlag; address1--; MemFlag[address1] = MemLabel[address1] = 0);

    for (address1 = sizeof LowByte / sizeof *LowByte; address1--; HighByte[address1] = LowByte[address1] = 0);

    for (address1 = sizeof MemReferenced / sizeof *MemReferenced; address1--; MemReferenced[address1] = 0);

    for (prog = *argv; *prog; prog++);

    for (; prog > *argv; prog--)
    if (*prog == '/')
    {
        prog++;
        break;
    }


    while (!optsfinished)
    {
#ifdef LONG_OPTIONS
        switch (getopt_long(argc, argv, "?b:B:L:r:R:h:l:a:d:p:g:t:eEnNsSjJoOcCmMvVwWxXqQGuU", cmd_options, &option_index))
        {
#else
        switch (getopt(argc, argv, "?b:B:L:r:R:h:l:a:d:p:g:t:eEnNsSjJoOcCmMvVwWxXqQGuU"))
        {
#endif /* LONG_OPTIONS */
        case -1:
        case ':':
            optsfinished = TRUE;
            break;

        case '?':
        case 'V':
            goto usage;

        case 'b':
            if (*optarg == '!')
            {
                ftype = MEM_PARAMETER;
                optarg++;
            }
            else if (*optarg == '?')
            {
                ftype = MEM_UNPROCESSED;
                optarg++;
            }
            else
                ftype = MEM_DATA;

            if (!sscanf(optarg, "%X-%X", &address1, &address2) || address1 > 65535 || address2 > 65535)
            {
                fprintf(stderr, "%s: Error in data block address range `%s'.\n\n", prog, optarg);
                goto usage;
            }

            for (; (ADDR_T)address1 != address2; address1++)
            {
                SetMemType(address1, ftype);
                SetMemFlag(address1);
            }

            SetMemType(address1, ftype);
            SetMemFlag(address1);

            break;

        case 'B':
            if (!((file = fopen(optarg, "rt"))))
            {
                fprintf(stderr, "%s: Could not open %s.\n", prog, optarg);
                return 2;
            }

            while (!feof(file))
            {
                if ('!' == (ftype = fgetc(file)))
                    ftype = MEM_PARAMETER;
                else if ('?' == ftype)
                    ftype = MEM_UNPROCESSED;
                else
                {
                    ungetc(ftype, file);
                    ftype = MEM_DATA;
                }

                if (!fscanf(file, "%X-%X\n", &address1, &address2) || address1 > 65535 || address2 > 65535)
                {
                    fprintf(stderr, "%s: Error in data block address file %s.\n", prog, optarg);

                    fclose(file);
                    return 3;
                }

                for (; (ADDR_T)address1 != address2; address1++)
                {
                    SetMemType(address1, ftype);
                    SetMemFlag(address1);
                }

                SetMemType(address1, ftype);
                SetMemFlag(address1);
            }

            fclose(file);
            break;

        case 'r':
            if (!sscanf(optarg, "%X", &address1) || address1 > 65535)
            {
                fprintf(stderr, "%s: Error in routine address `%s'.\n\n", prog, optarg);
                goto usage;
            }

            AddEntry(address1, address1, RTN_SURE);
            break;

        case 'R':
            if (!((file = fopen(optarg, "rt"))))
            {
                fprintf(stderr, "%s: Could not open %s.\n", prog, optarg);
                return 2;
            }

            while (!feof(file))
            {
                if (!fscanf(file, "%X\n", &address1) || address1 > 65535)
                {
                    fprintf(stderr, "%s: Error in data block address file `%s'.\n", prog, optarg);

                    fclose(file);
                    return 3;
                }

                AddEntry(address1, address1, RTN_SURE);
            }

            fclose(file);
            break;

        case 'L':
            if (0 > (listwidth = atoi(optarg)))
            {
                fprintf(stderr, "%s: Illegal listing width specified.\n\n", prog);
                goto usage;
            }

            break;

        case 'a':
            if (!strcmp(optarg, "disabled"))
                Options = (Options & ~M_ADDRESSES) | O_ADR_NOTHING;
            else if (!strcmp(optarg, "enabled"))
                Options = (Options & ~M_ADDRESSES) | O_ADR_ADRPFIX;
            else if (!strcmp(optarg, "dump"))
                Options = (Options & ~M_ADDRESSES) | O_ADR_ADR_DMP;
            else
            {
                fprintf(stderr, "%s: Unrecognized option for dumping addresses.\n\n", prog);
                goto usage;
            }
            break;

        case 'd':
            if (!strcmp(optarg, "poor"))
                Options = (Options & ~M_DATA_BLOCKS) | O_DBL_IGNORE;
            else if (!strcmp(optarg, "extended"))
                Options = (Options & ~M_DATA_BLOCKS) | O_DBL_DETECT;
            else if (!strcmp(optarg, "skip-scanning"))
                Options = (Options & ~M_DATA_BLOCKS) | O_DBL_NOSCAN;
            else if (!strcmp(optarg, "strict"))
                Options = (Options & ~M_DATA_BLOCKS) | O_DBL_STRICT;
            else
            {
                fprintf(stderr, "%s: Unrecognized option for detecting data blocks.\n\n", prog);
                goto usage;
            }
            break;

        case 'p':
            if (!strcmp(optarg, "all-nmos6502"))
                opset = all_nmos6502;
            else if (!strcmp(optarg, "rational-nmos6502"))
                opset = rational_nmos6502;
            else if (!strcmp(optarg, "useful-nmos6502"))
                opset = useful_nmos6502;
            else if (!strcmp(optarg, "traditional-nmos6502"))
                opset = traditional_nmos6502;
            else if (!strcmp(optarg, "r65c02"))
                opset = r65c02;
            else if (!strcmp(optarg, "standard-nmos6502"))
                opset = standard_nmos6502;
            else
            {
                fprintf(stderr, "%s: Unsupported instruction set `%s'.\n\n", prog, optarg);
                goto usage;
            }
            break;

        case 'e':
            Options &= ~B_LBL_ALWAYS;
            break;
        case 'E':
            Options |= B_LBL_ALWAYS;
            break;

        case 't':
            if (!strcmp(optarg, "ignore"))
                Options = (Options & ~M_ADR_TABLES) | O_TBL_IGNORE;
            else if (!strcmp(optarg, "detect-all"))
                Options = (Options & ~M_ADR_TABLES) | O_TBL_DETECT;
            else if (!strcmp(optarg, "detect-internal"))
                Options = (Options & ~M_ADR_TABLES) | O_TBL_NOEXT;
            else
            {
                fprintf(stderr, "%s: Unknown address table detection option `%s'.\n\n", prog, optarg);
                goto usage;
            }
            break;

        case 's':
            Options &= ~B_STM_DETECT;
            break;
        case 'S':
            Options |= B_STM_DETECT;
            break;

        case 'J':
            Options &= ~B_STK_BALANCE;
            break;
        case 'j':
            Options |= B_STK_BALANCE;
            break;

        case 'o':
            Options &= ~B_RSC_STRICT;
            break;
        case 'O':
            Options |= B_RSC_STRICT;
            break;

        case 'c':
            Options &= ~B_SCEPTIC;
            break;
        case 'C':
            Options |= B_SCEPTIC;
            break;

        case 'M':
            Options &= ~B_JMP_STRICT;
            break;
        case 'm':
            Options |= B_JMP_STRICT;
            break;

        case 'v':
            fVerbose = TRUE;
            break;

        case 'W':
            Options &= ~B_BRK_REJECT;
            break;
        case 'w':
            Options |= B_BRK_REJECT;
            break;

        case 'x':
            Options &= ~B_CROSSREF;
            break;
        case 'X':
            Options |= B_CROSSREF;
            break;

            /* new or altered dxa options */
        case 'n':
            Options &= ~B_LABCOL;
            break;
        case 'N':
            Options |= B_LABCOL;
            break;
        case 'q':
            Options &= ~B_SA_WORD;
            break;
        case 'Q':
            Options |= B_SA_WORD;
            break;
        case 'u':
            Options &= ~B_DETECT_BASIC;
            break;
        case 'U':
            Options |= B_DETECT_BASIC;
            break;
        case 'G':
            Options |= B_GET_SA;
            break;
        case 'g':
            Options &= ~B_GET_SA;
            if (!sscanf(optarg, "%X", &address1) || address1 > 65535)
            {
                fprintf(stderr, "%s: Error specifying starting address `%s'.\n\n", prog, optarg);
                goto usage;
            }
            StartAddress = address1;
            break;
        case 'l':
            if (!((file = fopen(optarg, "rt"))))
            {
                fprintf(stderr, "%s: Label file %s could not be opened for reading.\n\n", prog, optarg);

                goto usage;
            }

            while (!feof(file))
            {
                int tmp;
                ftype = fgetc(file);

                if (feof(file))
                    break;

                ungetc(ftype, file);

                /* This is the xa-compatible label scanner. */
                if (!fscanf(file, "%s%i,%i,%i", labelname, &address1, &tmp, &address2) || address1 > 65535 ||
                    !fgets(strig, sizeof strig, file))
                {

                LabelError:
                    fprintf(stderr, "%s: Error in label file %s.\n", prog, optarg);
                    fprintf(stderr, "Address(?): 0x%x ... Label(?): \"%s\"\n\n", address1, labelname);
                    fclose(file);
                    return 3;
                }

                for (scanner = labelname; *scanner; scanner++);
#if (0)
                if (scanner[-1] != '\n')
                    goto LabelError; /* line too long */
#endif

                while (--scanner > labelname &&
                       (*(unsigned char *)scanner < 32 || *(unsigned char *)scanner == 44)) /* and commas */
                    *scanner = 0; /* remove trailing control characters */

                for (scanner = labelname; *(unsigned char *)scanner < 32; scanner++)
                    if (!*scanner)
                        goto LabelError; /* label name missing */

                AddLabel(address1, scanner, address2 != 0, address2);
            }

            fclose(file);
        }
    }

    if (argc - optind > 1)
    {
        usage:
        fprintf(stderr, "dxa %s -- symbolic 65xx disassembler\n", DXA_VERSION);
        fprintf(stderr, "Based on d65 copyright (C) 1993-4 Marko M\"akel\"a\n");
        fprintf(stderr, "Changes for dxa copyright (c) 2006-19 Cameron Kaiser\n\n");
        fprintf(stderr, "Modifications for ++ version (c) 2015-2021 Matti 'ccr' Hamalainen <ccr@tnsp.org>\n\n");
        fprintf(stderr, "Usage: %s [options] [filename]\n", prog);
        return 1;
    }

    /* Fix, Need "rb" (binary mode) on Windows to avoid termintating on $1A */
    if (!(file = (argc - optind) ? fopen(argv[argc - 1], "rb") : stdin))
    {
        fprintf(stderr, "%s: Couldn't open input file.\n", prog);
        return 2;
    }

    if (Options & B_GET_SA)
    {
        StartAddress = (unsigned)fgetc(file);
        StartAddress |= (unsigned)fgetc(file) << 8;
    }

    if (feof(file))
    {
        fprintf(stderr, "%s: Error reading the file.\n", prog);
        return 3;
    }

    /* this doesn't work so well */
    /* AddEntry (StartAddress, StartAddress, RTN_SURE); */
    EndAddress = StartAddress + fread(&Memory[StartAddress], sizeof(char), 65536 - StartAddress, file);

    if (!feof(file))
    {
        if ((EndAddress = fread(Memory, sizeof(char), StartAddress, file)))
            fprintf(stderr, "%s: Warning: Input file caused an address overflow.\n", prog);

        if (!feof(file))
        {
            fprintf(stderr, "%s: Error: Input file is longer than 64 kilobytes.\n", prog);
            fclose(file);
            return 3;
        }
    }

    fclose(file);

    BasicHeaderLength = 0;
    if ((Options & B_DETECT_BASIC) && ((EndAddress - StartAddress) > 11) &&
        (StartAddress == 0x0401 ||      /* PET */
         StartAddress == 0x0801 ||      /* C64 */
         StartAddress == 0x1001 ||      /* VIC, 16, +4 */
         StartAddress == 0x1c01))       /* 128 */
    {
        /* If this file starts at a typical BASIC starting address, try to mark
           that as data. Bonus points for turning SYS xxxx into an entry point.
           For example, 1010 SYS 2061 comes out like this:
            .byt $0b,$08,$0a
            .byt $0a,$9e,$32
            .byt $30,$36,$31
            .byt $00,$00,$00
        */

        /* Heuristic: try to validate the line link address. If it seems sane,
        process further. */
        ADDR_T ll = Memory[StartAddress] + (Memory[StartAddress + 1] << 8);
        if ((ll > StartAddress) && (ll < EndAddress - 2) && Memory[ll] == 0)
        {
            ADDR_T offs = StartAddress + 5; /* byte after presumed first token */
            ADDR_T val = 0;
            ADDR_T i = 0;
            int ok = 0;

            /* See if there is an encoded SYS address in the first line. */
            if (Memory[StartAddress + 4] == 0x9e /* SYS */)
            {
                for (; offs < StartAddress + 11; offs++)
                { /* stop overrun */
                    if (Memory[offs] == 0x00)
                    {
                        ok = 1;
                        break;
                    }
                    if (Memory[offs] == 32) /* space */
                        continue;
                    if (Memory[offs] < 48 || Memory[offs] > 57)
                    {
                        ok = 0;
                        break;
                    }
                    if (val > 6553 || (val == 6553 && Memory[offs] > 53))
                    {
                        ok = 0; /* imminent overflow */
                        break;
                    }
                    val = (val * 10) + (Memory[offs] - 48);
                }

                if (ok && val > StartAddress && val < EndAddress)
                {
                    /* Address validates; mark it as an entry point. */
                    AddEntry(val, val, RTN_SURE);
                    if (fVerbose)
                        fprintf(stderr, "%s: SYS %d found, marking as entry point\n", prog, val);
                }
            }

            /* Try to find the end of BASIC text. Three nulls needed. */
            ok = 0;
            for (; offs < EndAddress; offs++)
            {
                if (Memory[offs] == 0)
                {
                    ok++;
                    if (ok == 3)
                    {
                        BasicHeaderLength = (offs - StartAddress) + 1;

                        /* Mark entire length of BASIC text as dead. */
                        for (i = StartAddress; i < offs; i++)
                        {
                            SetMemType(i, MEM_UNPROCESSED);
                            SetMemFlag(i);
                        }
                        if (fVerbose)
                            fprintf(stderr, "%s: BASIC text marked as dead through $%04x\n", prog, offs);
                        break;
                    }
                    else
                        continue;
                }
                ok = 0;
            }
            if (ok < 3 && fVerbose)
                fprintf(stderr, "%s: warning: couldn't find a valid end of BASIC text\n", prog);
        }
        else if (fVerbose)
            fprintf(stderr, "%s: warning: BASIC starting address $%04x, but invalid line\n", prog, StartAddress);
    }

    if (fVerbose)
        fprintf(stderr, "%s: disassembling $%04x-$%04x\n", prog, StartAddress, EndAddress);

    if (ScanSpecified())
    {
        fprintf(stderr, "\n%s: Invalid routine address(es) specified. Stop.\n", prog);

        return 4;
    }

    ScanPotentials();
    ScanTheRest();
    Collect();
    SearchVectors();
    Dump();

    return 0;
}