Nebojsa Urosevic - Breaking the Code: Ethdebug format for Smart Contract Debugging

EthereumZuri.ch 2024 · April 2024 · 24:09

Nebojsa Urosevic introduces the ethdebug format, a proposed standard for debugging data that links EVM execution back to the high-level code smart contract developers write. Without such a standard, reasoning about machine code output is impractical, which hurts smart contract quality and transparency. The EthereumZuri.ch 2024 talk explains how the format bridges machine code and human-readable source while accommodating the constraints of the Ethereum Virtual Machine.

Speakers

Topics

From EthereumZuri.ch 2024, hosted by EthereumZuri.ch.

Transcript

hey everyone uh thank you all for joining uh my name is nebish I'm one of the co-founders at tenderly and I'm also the lead from for our R&D team uh with me is Marco one of the R&D Engineers who's going to help me out with the live demo hopefully nothing goes wrong um today we're going to be talking about eth debug format for smart contract debugging um so what's what is eth debug format so a brief history some year and a half ago uh after the merge in bogot uh Dev connect uh we assembled as a group uh consisting of solidity

developers uh tooling company focus on building debuggers and tooling company focus on building uh security analysis tools and we started talking about like what's missing uh uh from the tooling perspective and what can we do to make lives of developer easier uh and we sat down with the C team and we started giving them feedback um fast forward some year and a half ago uh we now have this group that meets uh usually once every week where we talk about uh the specification of the format that solidity compiler and hopefully other evm compilers are going to implement in order for the tooling to uh get to the next level um so why

having a good debugger is important where for all the similar reasons for why the debugger is important in web 2 so all the essential stuff that the developer use it for such as uh navigating through the code um understanding how the execution is correlated with the source code uh what's going on on the high level uh and if uh an error occurs it helps them navigate to the to the to the bug much easier um on top of that in web 3 the stakes uh are much highers and the the bugs that uh uh could occur in production are usually

irreversible uh so one aspect of it is of course the security so like whenever you're developing uh uh smart contracts in most cases a lot of funds are going to be stored on them so you want to make sure that everything is working and that there are no bucks um the second thing is that you're usually interacting with some other third-party contracts uh navigating and managing complexity is one of the most important things in the web 3 uh so uh a good debugger should help with that as well and of course it enhances your confidence and reliability in your code once you push it to the production um all of these reasons uh uh

is why at tenderly some six years ago we built uh uh one of the first feature that we built was a debugger it sort of helped us solve some of our problems where we were uh building on hackathons Etc uh so what are some of the frustration that happened during the debugging and this is not just for our debugger this is basically for any debugger uh for any evm debugger so usually we get the incorrect source code for the given instruction uh this can happen uh in a case where SEL Source Maps don't point to the right line of code uh other things are uh misidentified function calls so what

solidity Optimizer can do sometimes is when they see uh when the optimizer sees two blocks of codes that are usually identical or almost identical it's going to use only one of those so like it's hard to navigate and uh uh it's hard for us to Showcase uh which exactly function is called in that case um error is error in the coding function parameters this this is something that quite often occurs uh it's usually hard for debuggers to pinpoint uh where the value of uh some parameter is is it on stack is it in memory is it in storage Etc and this is this is even harder for

some of the more complex structures uh uh in solidity or wiper uh bad or if any low local variable detection uh especially when the optimizer is enabled uh we are usually unaware of where the variable is on stack is it a pointer is it a value Etc um and there are numer other uh uh minor inconsistencies and errors um these are all some of the problems that we hope that uh e debug format is going to solve uh but so first of all what's a debug PL in format so it helps us make sense of the bite code that is being

executed on the evm uh so we all write code in the solidity or Viper maybe like less than 1% of the people write direct bite code uh but like for the people that write solidity or wiper we need some way to map instructions to high uh uh High language features such as for example function variables Etc um the thing is that when we talked to the folks at solidity they told us that compiler has most of this necessary information uh during the compile time but they don't output it um and so there is a need for this debug format uh uh due to the data complexity that exist uh

uh and various compilers differences uh and for all of these reasons uh uh we need the spec evm specific debugging standard uh so so it won't just be for the solidity it should uh all the evm languages uh should apply it uh and uh it should account for The Unique aspects of the evm such as 256bit uh memory locations gas data storage meth methods Etc um so what's the current state of the debugging today um in short uh uh there is no reliable way to build a debugger uh simply put the compiler doesn't output the

sufficient amount of information for us to build a reliable uh debugger or even in some cases existing data can often uh uh uh be unreliable or mislead us to thinking uh it's something it's not uh therefore we deploy some of theistic and guesswork but of course there are certain edge cases where those her istics or guess work that don't work simply don't work this is usually the case when the optimizer is enabled or where uh some or where U Optimizer is enabled uh in those cases we simply mislead the developers uh uh and we hope that uh uh debug

format is going to solve most of these problems um and the last thing is that mapping storage slots to variables is relatively complex we can do it it requires much more compute today but ideal uh solidity compiler can uh point us in the right direction uh of the storage slots um so some of the key problems that this format is aiming to solve uh so there are a lot of things but hopefully with four of these it can solve most of the uh problems for uh uh people building a debugger so first is type definition so uh ideally uh for every uh structure uh ideally for every

type we should have a structure of it we should have a name field declarations and the idea is to have an information on how to detect it like where it is is it on stack is it in memory is it in storage for some more complex structures ideally we would have pointers so for example if there is a a memory of array of structures we would have uh uh uh an information of where it is uh uh uh and what's the location and ideally uh something that can tell us how to decode it as you can see like pointers can be anywhere on stack memory storage now after d transient storage called Data return data

code um on top of that we would need Source locations so some way of mapping how each instruction on the evm corresponds to the high level source code um currently we use Source map for these but it can be quite unreliable uh especially in the cases where U Optimizer is enabled um and the other thing is what I talked at the beginning so uh in certain cases the optimizer is going to De duplicate some of the code segments ideally uh we can have information uh pointing us to the multiple locations and ideally of course tell us how to determine which one it is um and the

last thing is variable decoding uh one of the most ful thing I would say today is today for someone who is building a debugger uh is so some way to represent variables visible in the scope uh right now what we do is we basically deploy a bunch of characteristics in order to determine what variables are on stack uh it's quite hard for us to to know if the variable is optimize out meaning that it's not even used um so ideally debug format should tell us that or uh what could be even better is a way to reconstruct uh some of those optimized variables now um this format is in uh uh

we have first draft of the specification afterwards we're going to uh enroll in the implementation phase uh so uh after we have the implementation phase we're going to be talking like what we could have uh uh with better data so uh after we do that we can have 100% accurate C locations uh we can decode uh function call stack we can uh be certain about all the parameters uh that are inside of the functions like what's the input what's the output uh we can have all the variables in the scope uh we can know what which variables are optimized out meaning they are not used uh we can

decode fully decode memory uh uh we can have 100% accurate storage slot decoding um we could have evaluate expression without the need of the compiler or set them to watch variables uh and of course line by line debugging um and so much more um I mentioned at the beginning that we are going to have a quick demo so this is a a very very early proof of concept that we have built of how the f debugger might look like um onto Marco for the demo yeah hi everyone I'm going to show a small demo of um what we could have in

the future and hopefully it works let's see uh so here I have a basic rc20 token implemented uh so I'm going to try to run a function in the debugger and it's going to be a pretty basic function that just means some tokens in a weird way so let's go through it first we're going to be prompted to enter the parameters of the function which are some X and Y coordinates and then we're going to start debugging and the first thing that we can do is actually find the function it's this one fish for tokens and select where do we want to start debugging from

and let's hit a break point there um then we can hit continue and hopefully we do stop at the point um currently this is pretty hard to do in like without Source locations provided by the new format since there is a lot of instructions that are jumping around uh you can't really create Trace points anywhere so you need like some logical ways to stop the code but in the future we could possibly map like every instruction there is and just have a line by line debugger So currently what you can see here U is just the stack on the top I have some

htics passing the local variables you can see the inputed X and Y values um yeah I actually inputed them in decimal but they're showing up as hex values whatever they are the same trust me uh and you can see the local variable today's maximum that has been calculated uh we can turn on the instruction history we can see which instructions were run with some um additional information that can be useful to us uh we can maybe do some let's say uh watched Expressions if I put in something like x +

5 uh we should be able to EV evaluate that expression we should be able to do that with local variables so I'm going to add something like today's maximum plus one and hopefully it does evaluate uh we can maybe try to evaluate the weekday which is currently not in scope um since we are not U on that line yet so the evaluator can't do it but if we go a couple of instructions forward let's go jump instruction by instruction or maybe just skip to the next line and

next one we have the weekday which is in this case two um it's calculated in some way it's like days after start uh the moded by seven and the days after start is calculated like in a standard way you can see there um on the line 41 where we actually uh broke the or created a breakpoint uh some other interesting stuff that we can do that we can't really do now is debug U I'm going to turn on U debugging and let's cycle instructions until uh we hit some U code here we have if anybody saw utility zel did anyone actually see this file before

utility zel no one it's a file that solidity generates itself actually and it has like some crazy named functions you can see like function name uh that is long like over 128 characters and stuff like that um so yeah the solidity generates actually this U helper file that we can now debug we could also debug like any U file that you write yourself like U is a low-level Assembly Language or an intermediary language not many people use it but maybe we could create a debug debugger for it um if needed but currently it is pretty hard to do

without the debug format since uh we are missing a lot of information um that would allow us to do stuff like this um okay let's run off you'll debugging uh so we saw that we have evaluating expressions we have uh break points why not combine those two and have um conditional break points um I'm going to hit like a additional break point on line 49 so I added a conditional that uh if x a local variable is greater than three

which it is and uh if we hit continue we should hit that break point and we actually do that uh another thing that we we could do reliably uh is the decoding of one second uh we could do the decoding of function stack let's try to GA that uh so yeah we are currently in the function context of the fish for tokens function uh but if we go into the next line we are going to prepare the parameters and we're going to jump into the distance function and you can see the function stack has been let's say reliably created we can do that

currently but it's not completely reliable with the source map because um we often get some inlining or some D duplication that often messes this up and you can get really really weird the function Stacks from like the current Source Maps uh but yeah so even if we enter the Ule we can actually uh see the called Ule function and that's pretty useful I would guess uh also I think that very in my opinion useful is like this stack annotating uh you can inspect the stack and see which value is what uh these uh gray gray ones are not really that useful um anything

that's colored in is probably some useful information I color coded a local variables to be blue and you can see the third uh or or the second value on the stack uh is currently calculated as x + 256 um this could be actually pretty useful if you have some values that you don't know the origin of you can just check go to the point where you are interested in the uh value of the storage slot or sorry stack slot and then just check the uh check the expression that actually calculated it and it's actually pretty easy to do once you have reliable

source map pretty easy create to create thing like this you just remember a tree of expressions like every arithmatic operation you just track and construct a tree and just show it there in like a some representation um but yeah we can also check out the memory let's turn off these and turn on the memory uh this program doesn't actually like use um human readable memory it usually uses it to Generate random numbers you can see the function down there that uses the ABI and code packed and the ABI and code packed just dumps

everything into memory so it can be hashed and so you can see those values there uh if it were some like values that we could actually um read like humans it would be useful but in this example it's not really that great uh the place on like the memory on the left is actually uh just bytes and on the right you have the ask your representations if they actually mean anything but yeah uh so generally you could debug stuff like this and I'm just going to go through until we hit some of the inherited functions from the code in your C20 token and you can see here

on the stack everything is prepared you can see how many tokens you fished it's two tokens that's going to get minted you can see who is the caller who's going to actually receive the tokens and you can track the history of the values through the stack and see how they were generated so yeah I think this could be improved upon a lot but for now it's not I just let it run instruction by instruction with some pause and we hit the end and that's it um I'm going to let neish continue yeah uh thank you Marco um now we see how the how the things could look

like after the E debug format is implemented uh but what's going to happen beyond what are some of the benefits of it of course we're going to see improveed debugging efficiency which is going to result in a more productivity for the web 3 developer which is going to result in more applications and more users coming to the web 3 and trying things out um of course this format is supposed to be cross uh uh cross language so we could see some very interesting uh cross language tooling U come out uh and it's much easier for us uh as we would have to adopt just to the one uh debug format and not like for the each language

Implement debugger um for the security analysis tooling it's going to um it's going to of course enhance it uh there's so much more data that these analysis tool Cann now work with uh it's going to be much easier for the developers in the web 2 ecosystem to join in the web 3 because they're accustomed to having a full debugger and not like some uh uh some quasi implementation of it and uh with the rise of the AI uh uh and llms and with with the amount of data that we can now finally extract and have it visible from the onchain execution we can train these models uh uh and hopefully catch uh uh some of the

vulnerabilities or potential attacks before they even occur uh of course all of this wouldn't be possible without people uh uh coming and contributing to this uh debug format so if you're looking to contribute or if you have some idea or if you just want to brainstorm uh go to the EA GitHub format WE Post uh uh all of the updates there currently uh as mentioned we uh we have draft implementation of the specification and if you have any feedback on it uh please let us know um that's it thank you thank you so much thank you so much guys are there any questions we have time for two

questions can can we add uh any data for it for example like you have tested uh this contract against a local Lo maybe can I use against main net data like can I check an upgrade against my main net data currently so what are the changes of my storage uh The Stack Storage there mhm yeah sure uh that would be one of the Improvement that uh uh we could add with the debug format definitely basically anything in the execution layer that touches that that is relevant to the high level programming language we would be able to decode it