Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Help with inspection of `debug.traceback()` strings?

I still find the syntax for complex string inspection befuddling, so I’m hoping I can get some help with this issue.

Basically I need to compare the locations that two different commands are coming from. I get this info using debug.traceback().

For example, here’s a typical output from print(debug.traceback()):


stack traceback: SimpleButtons:178: in function 'button' greetingScreen:31: in function 'greetingScreen' Main:16: in global '_wrap_draw' SupportedOrientation :260: in function <SupportedOrientation :242>

As you can see, each level of the stack reports the tab it was called from, then the line number it was called from, and finally the function it was called from.

So here’s the situation I’m confronting: I have a current traceback that I need to compare to a table of stored tracebacks. I need to find the value in the table that most closely matches the current traceback.

My idea is to break each traceback string into a table of subtables, with each subtable breaking down the data in one level of the stack. I guess that means that what I need to know how to do is:

  • examine each line after “stack traceback” one by one
  • get the substring before the first colon
  • get the substring between the first and second colons
  • get the substring that’s between two delimiting characters, which can be either single quotes or brackets, at the end of the line

    So, for instance, with that knowledge I could turn the above traceback into this:


currentTraceback = { {“SimpleButtons”, ”178”, “button”}, {“greetingScreen”, “31”, “greetingScreen”}, {“Main”, “16”, “_wrap_draw”}, {“SupportedOrientation”, “260”, “<SupportedOrientation :242>“} }

…then, I think, it would be easy to compare each line of the current traceback to the equivalent line in a stored table of tracebacks.

…is there a substring wizard out there who can help me figure this out?

Comments

  • dave1707dave1707 Mod
    Posts: 9,730

    If you want to search thru the table for something specific, sort the table and then scroll thru it to find what you want.

  • Posts: 1,548
    @dave1707 I’m not sure what you mean.

    I have big complicated strings (the tracebacks) that I need to compare to other big complicated strings, and I think the best way to do that is to make tables out of the strings.

    When you write “sort the table and scroll through it to find what you want” it sounds like you think I already have those tables, which I don’t, and in fact what I’m asking is how to make those tables in the first place.
  • dave1707dave1707 Mod
    Posts: 9,730

    Are you trying to compare multiple trace back files to each other to see how the execution differs. I guess I don’t understand exactly what you’re trying to accomplish. I guess I’ll have to try the trace back and see what I get.

  • Posts: 1,548

    @dave1707 I need to be able to compare tracebacks to find the ones that are most identical.

    To do this I think I have to be able to compare tracebacks using several criteria:

    • How many layers in the stack?
    • What functions are in each layer?
    • What tab is each function called from?
    • What line inside each function is the call coming from?

    To make those comparisons, I think I have to be able to separate a traceback string into a set of tables that contain each of those criteria as separate elements.

  • dave1707dave1707 Mod
    Posts: 9,730

    @UberGoober Would something like this work. I just did some calls with trace back to try something. Let me know if you can work with this or if you need something changed.

    Do table.insert(tab,debug.traceback()) to put the trace back into a table.
    Then call fixTraceback() to format the info. This sorts the info. If you don’t want it sorted, remove the line table.sort(tab1,b) .

    Slide your finger to scroll up/down.

    fixTraceback does all the formatting and puts it into another table.

    viewer.mode=FULLSCREEN
    
    function setup()
        dy=0
        textMode(CORNER)
        fill(255)
        tab={}
        tab1={}
        for z=1,10 do
            aa() 
            bb()
        end
        fixTraceback()
    end
    
    function draw()
        background()
        for a,b in pairs(tab1) do
            text(b,10,HEIGHT-a*20+dy)
        end
    end
    
    function touched(t)
        if t.state==CHANGED then
            dy=dy+t.deltaY
        end
    end
    
    function fixTraceback()
        for a,b in pairs(tab) do
            b=string.gsub(b,"stack traceback:","")
            b=string.gsub(b,"\10"," ")
            b=string.gsub(b,"\09"," ")
            table.insert(tab1,b)
        end
        table.sort(tab1)
    end
    
    function aa()
        table.insert(tab,debug.traceback())
        bb()
        dd()
    end
    
    function bb()
        table.insert(tab,debug.traceback())
        cc()
    end
    
    function cc()
        table.insert(tab,debug.traceback())
    end
    
    function dd()
        table.insert(tab,debug.traceback())
        for v=1,2 do
            table.insert(tab,debug.traceback())
            print("p")
        end
    end
    
  • edited September 26 Posts: 1,548

    @dave1707 thank you for doing that. I’m not sure I fully understand how the string is being broken down before it’s reassembled.

    This is the kind of thing I need to be able to do, in the end:


    viewer.mode=FULLSCREEN function setup() t = getTraceback() tbl = tracebackTable(t) end function draw() background() end function getTraceback() return debug.traceback() end function tracebackTable(t) --some code here that ends up making this table: return { {"Main2", "16", "getTraceback"}, {"Main2", "5", "setup"}, } end

    …would I be able to do that with the way you’re separating the strings?

  • dave1707dave1707 Mod
    Posts: 9,730

    It took a lot of fixing on the trace back, but this looks right.

    viewer.mode=STANDARD
    
    function setup()
        dy=0
        textMode(CORNER)
        fill(255)
        tab={}
        tab1={}
        for z=1,10 do
            aa()
            bb()
            dd()
        end
        fixTraceback()
    end
    
    function draw()
        background()
        for a,b in pairs(tab1) do
            text(b,10,HEIGHT-a*20+dy)
        end
    end
    
    function touched(t)
        if t.state==CHANGED then
            dy=dy+t.deltaY
        end
    end
    
    function fixTraceback()
        for a,b in pairs(tab) do
            b=string.gsub(b,"stack traceback:","")
            b=string.gsub(b,"in function","")
            b=string.gsub(b,"  ","")
            b=string.gsub(b,"\10\09","{")
            b=string.gsub(b,"'{","'},{")
            b=string.gsub(b,":",",")
            b=string.gsub(b,"},",":")
            b=b.."}"
            b=string.gsub(b,",","','")
            b=string.gsub(b,"''","'")
            b=string.gsub(b,":","},")
            b=string.gsub(b,"{","{'")
            b=string.gsub(b,"'",'"')
            table.insert(tab1,b)
        end
        --table.sort(tab1)
    end
    
    function aa()
        table.insert(tab,debug.traceback())
    end
    
    function bb()
        table.insert(tab,debug.traceback())
        cc()
    end
    
    function cc()
        table.insert(tab,debug.traceback())
    end
    
    function dd()
        table.insert(tab,debug.traceback())
        for v=1,2 do
            table.insert(tab,debug.traceback())
            print("p")
        end
    end
    
  • Posts: 1,548

    @dave1707 this looks great and I can’t wait to try it out.

  • dave1707dave1707 Mod
    Posts: 9,730

    @UberGoober Heres another version using gmatch.

    viewer.mode=STANDARD
    
    function setup()
        tab={}
        tab1={}
        for z=1,1 do
            dd()
        end
        for a,b in pairs(tab) do
            print(b)
        end
    
        pos=0
        for w,r in pairs(tab) do
            pos=pos+1
            sub=1
            tab1[pos]={}
            for a,b,c in string.gmatch(r,"(%a*):(%d*): in function '(%a*)'") do
                tab1[pos][sub]=a..","..b..","..c
                print("["..pos.."]["..sub.."]  ",tab1[pos][sub])
                sub=sub+1
            end    
        end
    end
    
    function dd()
        table.insert(tab,debug.traceback())
        ee()   
        table.insert(tab,debug.traceback())
        ff()
    end
    
    function ee()
        table.insert(tab,debug.traceback())
        ff()    
    end
    
    function ff()
        table.insert(tab,debug.traceback())
    end
    
  • edited September 30 Posts: 1,548

    @dave1707 I tried it and I realized I’ve pointed in the wrong direction, because I was unclear in an important way: I actually am trying to get tables made, not strings that look like tables when they’re printed out.

    I’ve tried altering the first code you set up, to demonstrate what I’m after, and I think it might be working, but I also think I’ve turned your nice compact code into a sprawling mess because ultimately it was just trial and error for me:


    viewer.mode=STANDARD function setup() dy=0 textMode(CORNER) fill(255) tab={} tab1={} -- for z=1,10 do for z=1,1 do aa() bb() dd() end fixTraceback() local splitPerLine, linesSplitByWord = {}, {} splitPerLine = splitIntoOneTablePerStackLine(tab1[1]) for i = 1, #splitPerLine do table.insert(linesSplitByWord, splitIntoSeparateItems(splitPerLine[i])) end print(table.unpack(linesSplitByWord)) print(linesSplitByWord[1], ": ", table.unpack(linesSplitByWord[1])) print(linesSplitByWord[2], ": ", table.unpack(linesSplitByWord[2])) end function draw() background() for a,b in pairs(tab1) do --text(b,10,HEIGHT-a*20+dy) end end function touched(t) if t.state==CHANGED then dy=dy+t.deltaY end end function splitIntoOneTablePerStackLine(fixedStackString) local stackLines = {} local subString, indexToSplitAt while true do indexToSplitAt = string.find(fixedStackString, "}") if indexToSplitAt == nil then goto outOfWhile else local upToClosingBracket = string.sub(fixedStackString, 2, indexToSplitAt - 1) table.insert(stackLines, upToClosingBracket) fixedStackString = string.sub(fixedStackString, indexToSplitAt + 1) end end ::outOfWhile:: return stackLines end function splitIntoSeparateItems(stackLine) local returnTable = {} local stackLineString local firstComma = string.find(stackLine, ",") table.insert(returnTable, string.sub(stackLine, 1, firstComma - 1)) stackLineString = string.sub(stackLine, firstComma + 1) local secondComma = string.find(stackLineString, ",") table.insert(returnTable, string.sub(stackLineString, 1, secondComma - 1)) stackLineString = string.sub(stackLineString, secondComma + 1) table.insert(returnTable, string.sub(stackLineString, 1)) return returnTable end function fixTraceback() for a,b in pairs(tab) do b=string.gsub(b,"stack traceback:","") b=string.gsub(b,"in function","") b=string.gsub(b," ","") b=string.gsub(b,"\10\09","{") b=string.gsub(b,"'{","},{") b=string.gsub(b,":",",") b=string.gsub(b,"},",":") b=b.."}" b=string.gsub(b,",",",") b=string.gsub(b,"''","") b=string.gsub(b,":","}") b=string.gsub(b,"{","{") b=string.gsub(b,"'",'') table.insert(tab1,b) end end function aa() table.insert(tab,debug.traceback()) end function bb() table.insert(tab,debug.traceback()) cc() end function cc() table.insert(tab,debug.traceback()) end function dd() table.insert(tab,debug.traceback()) for v=1,2 do table.insert(tab,debug.traceback()) -- print("p") end end

    I’m pretty positive there’s a better way to do it though.

  • dave1707dave1707 Mod
    Posts: 9,730

    @UberGoober I haven’t looked at your code yet, but the last example I posted creates each trace back in a multi dimensional table. If a trace back results in 3 calls, then there will be 3 occurances tab [1] [3] . If the next trace back has 5 occurances then it will be tab [2] [5] and so on. Each occurrence will have the string function,line number,function .

  • Posts: 1,548

    When I run it it seems to leave off the tab name.

  • dave1707dave1707 Mod
    Posts: 9,730

    This is what I get when I run it. Here’s the first 2 trace backs and what I put in the table for them. What are you running this on. Maybe different devices have different trace back formats. I’m on an iPad Air 3.

    stack traceback:
    Main:27: in function 'dd'
    Main:8: in function 'setup'

    stack traceback:
    Main:34: in function 'ee'
    Main:28: in function 'dd'
    Main:8: in function 'setup'

    [1][1] Main,27,dd
    [1][2] Main,8,setup
    [2][1] Main,34,ee
    [2][2] Main,28,dd
    [2][3] Main,8,setup

  • dave1707dave1707 Mod
    Posts: 9,730

    Run this code and compare it to what I have. Just wondering if there are some invisible characters you have that I don’t. The line numbers might be different depending on how many blank lines you have before the code.

    function setup()
        tab={}
        tab1={}
        dd()
        print(tab[1])
        for z=1,#tab[1] do
            v=string.sub(tab[1],z,z)
            table.insert(tab1,string.byte(v))
        end
        print(table.concat(tab1," "))
    end
    
    function dd()
        table.insert(tab,debug.traceback())
    end
    
    stack traceback:
        Main:17: in function 'dd'
        Main:7: in function 'setup'
    
    
    115 116 97 99 107 32 116 114 97 99 101 98 97 99 107 58 10 9 77 97 105 110 58 49 55 58 32 105 110 32 102 117 110 99 116 105 111 110 32 39 100 100 39 10 9 77 97 105 110 58 55 58 32 105 110 32 102 117 110 99 116 105 111 110 32 39 115 101 116 117 112 39
    
  • edited September 30 Posts: 1,548

    My results:


    stack traceback: M4:17: in function 'dd' M4:7: in function 'setup' 115 116 97 99 107 32 116 114 97 99 101 98 97 99 107 58 10 9 77 52 58 49 55 58 32 105 110 32 102 117 110 99 116 105 111 110 32 39 100 100 39 10 9 77 52 58 55 58 32 105 110 32 102 117 110 99 116 105 111 110 32 39 115 101 116 117 112 39

    (iPad Pro)

  • Posts: 1,548

    Weirder and weirder.

    I tried to explicate the variables in your gmatch code so that I could understand it easier. This is what it looks like after that:


    viewer.mode=STANDARD function setup() tracebacksRaw={} tracebacksProcessed={} for z=1,1 do startCallChain() end for _, traceback in pairs(tracebacksRaw) do print(traceback) end traceNumber=0 for _, traceback in pairs(tracebacksRaw) do traceNumber=traceNumber+1 stackLine=1 tracebacksProcessed[traceNumber]={} for tabName,lineNumber,functionName in string.gmatch(traceback,"(%a*):(%d*): in function '(%a*)'") do tracebacksProcessed[traceNumber][stackLine]=tabName..","..lineNumber..","..functionName print("["..traceNumber.."]["..stackLine.."] ",tracebacksProcessed[traceNumber][stackLine]) stackLine=stackLine+1 end end end function startCallChain() table.insert(tracebacksRaw,debug.traceback()) function2InChain() table.insert(tracebacksRaw,debug.traceback()) function3InChain() end function function2InChain() table.insert(tracebacksRaw,debug.traceback()) function3InChain() end function function3InChain() table.insert(tracebacksRaw,debug.traceback()) end

    …but I did something wrong, because look at the output (attached).

    You can see that the tracebacks are being captured accurately, but when they’re chopped up only “setup” and “startCallChain” are mentioned —there’s no sign of the other two calls in the chain.

    Can you see where I went wrong? I’ve double-checked it and it looks the same to me.

  • dave1707dave1707 Mod
    edited September 30 Posts: 9,730

    @UberGoober I tried it on my iPad Pro and I got the same results as on my iPad Air 3. I also tried it on my iPhone 8 SE and got the same results as the Air 3. Not sure why you’re getting M4 and not Main. I can probably change the parameters in the gmatch to account for a number in the first position (M4). Let me try some function names that have numbers in them and see what happens.

    PS. I tried some numbers in the function names and things didn’t turn out too well. Didn’t get anything like what I should have. So let me change the gmatch parameters and see what happens I guess I should also account for underscores in a function name.

    The gmatch parameters are pretty specific as to what it looks for and if it doesn’t match exactly, it ignores stuff.

    Try changing the gmatch to this.

            for a,b,c in string.gmatch(r,"(%g*):(%g*): in function '(%g*)'") do
    
  • dave1707dave1707 Mod
    Posts: 9,730

    I ran your latest code and I changed the gmatch parameters to %g* and this is what I got for the first 2 tracebacks.

    stack traceback:
        Main:27: in function 'startCallChain'
        Main:7: in function 'setup'
    
    
    stack traceback:
        Main:34: in function 'function2InChain'
        Main:28: in function 'startCallChain'
        Main:7: in function 'setup'
    
    
    [1][1]      Main,27,startCallChain
    [1][2]      Main,7,setup
    
    [2][1]      Main,34,function2InChain
    [2][2]      Main,28,startCallChain
    [2][3]      Main,7,setup
    
  • Posts: 1,548
    “M2” refers to a tab I added after Main, it stands for “Main2”.

    When you post a bunch of different code on a single theme, I tend to make it one project and just add each piece of code on its own page, and then re-order the pages for whichever one I want to run at that time.
  • dave1707dave1707 Mod
    Posts: 9,730

    I never thought of tab name, that makes sense. Have you tried changing the %a* and %d* to %g* . That seems to work better.

  • Posts: 1,548
    Changing to g* did the trick!

    You’re a gmatch genius.
  • dave1707dave1707 Mod
    Posts: 9,730

    @UberGoober I wish I was a gmatch genius. I usually struggle when I’m doing gmatch. It’s more hit or miss before I get it to work.

Sign In or Register to comment.