py8dis - a programmable static tracing 6502 disassembler in Python

handy tools that can assist in the development of new software
User avatar
TobyLobster
Posts: 618
Joined: Sat Aug 31, 2019 7:58 am
Contact:

Re: py8dis - a programmable static tracing 6502 disassembler in Python

Post by TobyLobster »

Maybe this...

Code: Select all

for i in range(5):
    rts_code_ptr(0x8e18+i, 0x8e1d+i)
Untested.
User avatar
KenLowe
Posts: 4675
Joined: Mon Oct 18, 2004 5:35 pm
Location: UK
Contact:

Re: py8dis - a programmable static tracing 6502 disassembler in Python

Post by KenLowe »

Ok, that worked, thanks!
User avatar
KenLowe
Posts: 4675
Joined: Mon Oct 18, 2004 5:35 pm
Location: UK
Contact:

Re: py8dis - a programmable static tracing 6502 disassembler in Python

Post by KenLowe »

Ok, another couple of questions:

1. I've tried to make a comment box but the lower part of the box doesn't appear, because it's duplicate of the upper part of the box.
2. I've tried to remove a couple of auto 'overlapping' comments, but it hasn't worked.

How do I make these work?

Snippet of control file:

Code: Select all

from commands import *
from trace import *
import acorn

load(0x8000, "nfs-3.34.rom", "6502")
move(0x0400, 0x934c, 0x300)
set_output_filename("nfs334.6502")

comment(0x8000, "*****************************************************")
comment(0x8000, "If patching for Master 128, set the following:")
comment(0x8000, "econet_station_id = &2000 (or any address you choose)")
comment(0x8000, "econet_INTOFF     = &fe38")
comment(0x8000, "econet_INTON      = &fe3c")
comment(0x8000, "*****************************************************")
blank(0x8000)

acorn.bbc()
acorn.is_sideways_rom()

no_automatic_comment(0x81e9)
comment(0x81e9, "Comment removed")
no_automatic_comment(0x8208)
no_automatic_comment(0x820b)
no_automatic_comment(0x8215)
no_automatic_comment(0x8218)
Snippet of PyDis output:

Code: Select all

    org &8000

; *****************************************************
; If patching for Master 128, set the following:
; econet_station_id = &2000 (or any address you choose)
; econet_INTOFF     = &fe38
; econet_INTON      = &fe3c

; Sideways ROM header
; &8000 referenced 2 times by &048b, &9bb8

<---SNIP--->

.c81e6
    jsr print_message_and_fall_through                                ; 81e6: 20 3b 85     ;.
; Comment removed
; overlapping: eor l0063                                              ; 81e9: 45 63       Ec
    equs "Econet Station "                                            ; 81e9: 45 63 6f... Eco

    lda l0d62                                                         ; 81f8: ad 62 0d    .b.
    jsr sub_c85af                                                     ; 81fb: 20 af 85     ..
    lda #&20 ; ' '                                                    ; 81fe: a9 20       .
    bit econet_adlc_address_1                                         ; 8200: 2c a1 fe    ,..
    beq c8212                                                         ; 8203: f0 0d       ..
    jsr print_message_and_fall_through                                ; 8205: 20 3b 85     ;.
; overlapping: jsr l6f4e                                              ; 8208: 20 4e 6f     No
    equs " No Clock"                                                  ; 8208: 20 4e 6f...  No
; overlapping: jsr l6c43                                              ; 820b: 20 43 6c     Cl

    nop                                                               ; 8211: ea          .
; &8212 referenced 1 time by &8203
.c8212
    jsr print_message_and_fall_through                                ; 8212: 20 3b 85     ;.
; overlapping: ora la00d                                              ; 8215: 0d 0d a0    ...
    equb &0d, &0d                                                     ; 8215: 0d 0d       ..
SteveF
Posts: 1663
Joined: Fri Aug 28, 2015 9:34 pm
Contact:

Re: py8dis - a programmable static tracing 6502 disassembler in Python

Post by SteveF »

Hi Ken,

I haven't tested the following, but you could try:

1. comment(0x8000, """
*****************************************************
If patching for Master 128, set the following:
econet_station_id = &2000 (or any address you choose)
econet_INTOFF = &fe38
...
""")

2. nonentry(0x1234) at the address of the unwanted overlapping instruction. It is possible (but by no means certain) that this unwanted overlap indicates some sort of error elsewhere in your control file (py8dis is, barring bugs, following some seemingly legitimate control flow to get to the overlapping instruction), but there's no real harm in trying this. The "right" fix (again assuming py8dis isn't at fault) might be to use nonentry() elsewhere where that bad trace starts - just as a random example, if code does "bcc" but it knows C is always clear, data could follow the bcc but py8dis doesn't know this and will trace that data as code.

Edit: With respect to point 1, it looks like this is an explicit behaviour in comment_binary() in disassembly.py:

Code: Select all

    # Avoid adding the same comment multiple times at the same address
    for entry in annotations[binary_addr]:
        if entry.as_string(binary_addr) == new_comment.as_string(binary_addr):
            return
I guess this behaviour is there so automatically generated comments don't get duplicated. We could add an optional argument "allow_duplicate" (probably defaulting to False if not specified) to comment() which allows you to explicitly say "I don't care if this is a duplicate, add it anyway". If we did this it might also be worth adding a new top level convenience function ndcomment() which just wraps comment() but sets allow_duplicate to True. (I don't like the name ndcomment, it's just to get the idea across.)

Edit: With respect to point 2, it may be that the hook handling print_message_and_fall_through is not quite right now I look at your actual output - but this is just a guess. Guessing further, it may be returning the address of the first byte of the following string, when it should return the address of the first instruction executed when the subroutine returns. (Or if you don't have a hook handling print_message_and_fall_through, you probably need one, but I suspect you do have one.)
SteveF
Posts: 1663
Joined: Fri Aug 28, 2015 9:34 pm
Contact:

Re: py8dis - a programmable static tracing 6502 disassembler in Python

Post by SteveF »

I see you've actually posted your disassembly on another thread - nice work! I see you don't have a hook, I've attached a complete tweaked nfs334.py but the key change I'd suggest is:

Code: Select all

hook_subroutine(0x853b,"print_message_and_fall_through", stringhiz_hook)
instead of:

Code: Select all

label(0x853b, "print_message_and_fall_through")
The attached version has a few tweaks to avoid double-classification glitches where this code tries to classify things you are explicitly classifying.

On a different note, you have a case mismatch between the filename of the ROM in git and the filename in the control file, which I had to manually fix to use it on Linux - it's not a big deal, but it would be nice if you could tweak this to be consistent.

Edit: I think it should actually be stringhi_hook not stringhiz_hook. I haven't updated the attached version, you may want to try making this change yourself.
Attachments
nfs334-tweaked.zip
(1.7 KiB) Downloaded 2 times
User avatar
KenLowe
Posts: 4675
Joined: Mon Oct 18, 2004 5:35 pm
Location: UK
Contact:

Re: py8dis - a programmable static tracing 6502 disassembler in Python

Post by KenLowe »

Thanks for the feedback.

The change you suggested for the duplicate comment() line issue worked well. I didn't realise you could split a function over several lines like you did.

And the hook_subroutine() function is exactly what I needed. I re-read the instructions on your github page and now have a better understanding of how that function works. As you indicated, I think I should be using stringhi_hook, as the printing code is checking for end of message with a BMI (ie high bit set) instruction - although, in practice, both stringhi and stringhiz will probably work.

Since you didn't make any comment on my repeated use of the following, I'm guessing means that I've done that correctly. I did wonder if there was a more efficient way of doing it:

Code: Select all

label(addr,"label")
expr(addr1, make_lo("label"))
expr(addr1+2, make_hi("label"))
I'll fix the file name case mismatch as well.
SteveF
Posts: 1663
Joined: Fri Aug 28, 2015 9:34 pm
Contact:

Re: py8dis - a programmable static tracing 6502 disassembler in Python

Post by SteveF »

Thanks Ken, I'm glad that worked for you.

I don't think that repeated code fragment is bad, py8dis has no way to work this out for itself so you just have to tell it - unless I'm forgetting something, there is no py8dis cleverness you are not taking advantage of here.

Do remember that if you find yourself repeating a pattern of py8dis commands, nothing stops you writing your own Python function to factor out the duplication:

Code: Select all

def mllh(addr, label, lo_ref, hi_ref=None): # mllh notionally stands for make_label_lo_hi
    if hi_ref is None:
        hi_ref = lo_ref + 2
    label(addr, label)
    expr(lo_ref, make_lo(label))
    expr(hi_ref, make_hi(label))
    
mllh(0x9d94, "l9d94", 0x9d8e)
mllh(0x9db2, "l9db2", 0x9dac)
...
Not tested, but you get the idea. Whether this is worth doing or not is up to you. (At the risk of stating the obvious, you can also define trivial functions which just forward calls on to a standard function in order to do things like have short names - maybe c() instead of comment() - or to specify a different default value for an optional parameter.)
User avatar
KenLowe
Posts: 4675
Joined: Mon Oct 18, 2004 5:35 pm
Location: UK
Contact:

Re: py8dis - a programmable static tracing 6502 disassembler in Python

Post by KenLowe »

I like the idea of adding that function. Do I just add it to the control file? I've tried the following:

Code: Select all

from commands import *
from trace import *
import acorn

def mllh(addr, label, lo_ref, hi_ref=None): # mllh notionally stands for make_label_lo_hi
    if hi_ref is None:
        hi_ref = lo_ref + 2
    label(addr, label)
    expr(lo_ref, make_lo(label))
    expr(hi_ref, make_hi(label))

load(0x8000, "NFS-3.34.rom", "6502")
move(0x0400, 0x934c, 0x300)
set_output_filename("nfs334.6502")
but I get the following error:

Code: Select all

py8dis>python nfs334.py > nfs334.asm
Traceback (most recent call last):
  File "nfs334.py", line 60, in <module>
    mllh(0x9d94, "l9d94", 0x9d8e)
  File "nfs334.py", line 8, in mllh
    label(addr, label)
TypeError: 'str' object is not callable
Edit: removing the line label(addr, label) from the mllh() function and keeping it in the main body of the control file fixes the error, but I'm not sure why it doesn't work inside the function? The expr() function in the new mllh function accepts the label :?.
SteveF
Posts: 1663
Joined: Fri Aug 28, 2015 9:34 pm
Contact:

Re: py8dis - a programmable static tracing 6502 disassembler in Python

Post by SteveF »

D'oh! It's because one of the parameters is called "label", which hides the global function called label. If you change it to:

Code: Select all

def mllh(addr, label_name, lo_ref, hi_ref=None): # mllh notionally stands for make_label_lo_hi
    if hi_ref is None:
        hi_ref = lo_ref + 2
    label(addr, label_name)
    expr(lo_ref, make_lo(label_name))
    expr(hi_ref, make_hi(label_name))
it will probably work. But I haven't tested it again, because second time lucky, right? :-)
User avatar
KenLowe
Posts: 4675
Joined: Mon Oct 18, 2004 5:35 pm
Location: UK
Contact:

Re: py8dis - a programmable static tracing 6502 disassembler in Python

Post by KenLowe »

I just figured that out! I changed the name to fnlabel, and, yes it works!

Thansk!
Post Reply

Return to “development tools”