aleclm
Joined 12 years ago
Joined 12 years ago
Roadmap item: https://rev.ng/roadmap#feature-798
Design pad: https://pad.rev.ng/s/eDHi2PUoP#
I think this a bit of a misguided question. The hardware has a precise semantic defined, usually. QEMU's << behaves similarly to C (undefined behavior for rhs > 32), but this means that the lifter (still QEMU) will account for this and emit code preserving the semantics.
tl;dr: the code we emit should do the right thing depending on what the original instruction did, without making assumptions on what happens in case of C undefined behaviors.
> Ghidra's type system lacks function pointer types
Weird limitation, we support those.
> it doesn't seem to understand stack slot reuse
That's a tricky one. We're now re-designing certain parts of the pipeline to enable LLVM to promote stack accesses to SSA values, which basically solves the stack slot reuse. This is probably one of the most important features experienced reversers ask for.
> that language need not be C--
Making up your own language is temptation one should resist.
Anyway, we're rewriting our backend using an MLIR dialect (we call it clift) which targets C but should be good enough to emit something "similar to C but slightly different". It might make sense to have a different backend there. But a "standard C" backend has to be the first use case.
We thought about emitting C++, it would make our life simpler. But I think targeting non-C as the first and foremost backend would be a mistake.
Also, a Python backend would be cool.
> Analysis necessarily involves...
I would be interested in discussing more what exactly you mean here. Why don't you join our discord server?
> I'd absolutely love to be able to import C++ header files to fill in known structure types
We have a project for importing from header files. Basically we want use a compiler to turn them into DWARF debug symbols and then import those. Not too hard.
It has been working very well. Two regrets:
1. Not rebasing our fork of QEMU for years has put us in a bad spot. But just today a member of our team managed to lift stuff with the latest QEMU. And he has also been able to lift Qualcomm Hexagon code, for which we helped to add support in QEMU. Eventually we'll be the first proper Hexagon decompiler :)
2. Focusing too much on QEMU led our frontend to be tightly coupled with QEMU. It will now take some effort to enable support for additional frontends, non-QEMU based. But not impossible: our idea is to let user add support for a new architecture by defining, in C, a struct for the CPU state and a bunch of functions acting on it. That's it. No need to learn any internal representation.
tl;dr QEMU was a great choice, it worked so well that we didn't work on that part of the codebase for too much time and now there's some technical debt there. But we're addressing it.
Our goal is: omit all the casts that can be omitted without changing the semantics according to C. In fact, we have a PR doing exactly this (still on the old repo, hopefully it will go in soon).
But, how can you expect to be able to be strict with what C allows you to do implicitly, if you're not even emitting valid C? For instance, thanks to the fact that we emit valid C, we could test if the assembly emitted by a compiler is the same before and after removing redundant casts.
My point is that emitting valid C is kind of a prerequisite for what you're asking, a rather low bar to pass, but that, in practice, no mainstream decompiler passes. It's pretty obvious the decompiled code will often be redundant and outright wrong if you don't even guarantee it's syntactically valid. Then clearly it's not a panacea, but it's an important design criterion and shows the direction we want to go.
As for comments: we still haven't implemented inline comments, but they will be attached to program addresses, so they will be available both in disassembly and decompiled C. It's not very hard to do, but that needs some love.
You're right, but after that there's a link to the docs where we say to use `./revng`. The blog post is for the impatient :) On the long run the docs is what most people will look at.
I don't think we want to support use cases that might break system packages too. If you set LD_LIBRARY_PATH to a directory where you have an LLVM installation, that might break any system program using LLVM too... Why should we try to fix that using `DT_RPATH` (which is a deprecated way of doing things) when system components don't do it?
We might cleanup the environment from LD_LIBRARY_PATH and other stuff, that might be a sensible default, yeah. Also we might have some sanity check printing a warning if weird libraries are pulled in.
But it's hard to take a decision without a specific use case in mind. If you have an example, bring it forward and I'm happy to discuss what should be the right approach there.
Imagine this being done automatically, across all of the binary. If you pass a pointer to another function the type is correct and you build the type from all the functions using it.
Then obviously the user needs to fix things, but boostrapping can definitely be hugely improved.
In truth, we suggest to do that only so you use the GCC we distribute for the demo binary. The actual way this is intended to be used is through the `./revng` script. In that way, the environment changes only affect the invocation of `revng`.
This is documented here: https://docs.rev.ng/user-manual/working-environment/ We should probably add a warning about `source ./environment`.
Now, let's get to each of your comments :D
> though thankfully not LD_LIBRARY_PATH
We spent a lot of time to have a completely self-contained set of binaries where each ELF refers to its dependencies through relative paths. LD_LIBRARY_PATH is evil.
> Mostly prefixed "HARD_"
Those are just used by our compiler wrappers, I don't think those environment variables collide with anything in practice.
> It sets `AWS_EC2_METADATA_DISABLED="true"`
Original discussion: https://github.com/revng/revng/pull/309#discussion_r12805759...
I guess we could patch the AWS SDK to avoid this. Anyway, it affects only when rev.ng is running in the cloud.
> export RPATH_PLACEHOLDER=... > export HARD_FLAGS_CXX_CLANG=...
Those are used when linking binaries translated by revng. If you're not interested in end-to-end binary translation, they don't matter.
> it means 'runpath' which is a similar but much less useful construct
We specifically want DT_RUNPATH. DT_RPATH is deprecated and there might an use case for overriding our libraries with LD_LIBRARY_PATH.
> There's a good chance you can completely remove the environment mangling
I think your observations concerning "mangling the environment" are only valid for non-private environment variables. The following variables are private: RPATH_PLACEHOLDER, HARD_*, REVNG_*. Also, they are all only for binary translation purposes. We could push them down into some smaller-scoped compiler wrappers, but those make sense only if we can get rid of environment entirely, which we can't because we ship Python.
> a combination of setting different flags when building clang
No, the flags also affect the linker and there's some features of our wrappers that cannot simply be burned in. We can push them in more private places, though.
> a lot of failure modes can be removed > libc++abi and libunwind can and probably should be statically linked into the libc++
We no longer have issues with that, our build system is pretty reliable in that regard. LLVM is just one of the components, these things need to work robustly in general, and they do (with quite some effort).
You seem to be wary of using dynamic linking, we put some effort in it, now it works pretty good and always looks up things in the right place, and without ever hardcoding absolute paths anywhere, nor any install phase that "patches" the binaries. The unpacked directory can be moved wherever you want.
> I am completely out of patience with distributing dynamically linked programs on Linux
You're thinking of some other solution, our solution does not use LD_LIBRARY_PATH and all the binaries reference each other in a robust way using `$ORIGIN`. Try:
./root/bin/python ./root/bin/revng artifact --help
It works.But again, doing `source environment` is mostly for demo purposes, in the actual use case, you just do `./revng` and your environment is untouched.
We ship our Python, but you don't have to use it: you're supposed to just do ./revng (or interact over the network in daemon mode).
Our approach is: use whatever tool you like for scripting as long as it can parse our YAML project file, make changes to it, and then invoke `./revng artifact` (or interact with the daemon): https://docs.rev.ng/user-manual/model-tutorial/
Result: we get to use our Python version (the latest) and you get to use whatever language you like. Then we'll provide on pypi wrappers that help you with that and are compatible with large set of Python versions.
tl;dr Don't `source ./environment`, use `./revng`.
> Thanks again for shipping, and I hope some of the above feedback is helpful!
I'm happy there's someone that cares about this :D
Our next big iteration of this might involve simplifying things a lot by adopting nix + mount namespace to make /nix/store available without root.
Maybe this is not the right place for discussing this, we can chat on our discord server if you'd like :)
We haven't looked into it yet, but the automatic data structure recognition might help.
Frankly, Rust looks particularly scary: https://media.ccc.de/v/37c3-11684-rust_binary_analysis_featu...
In QEMU parlance, these are helper functions, and they have actual implementations. But for decompilation purposes, you don't need to implement them. You just need to know how they interact with the registers.