Looking for ideas on how to make llvm-objdump handle both arm and thumb disassembly from the same object file

Hello Tim, Rafael, Renato and llvmdev,

I’m working to get llvm-objdump handle both arm and thumb disassembly from the same object file similarly to how darwin’s otool(1) works. And I’m looking for implementing direction. I spoke to Jim Grosbach about some ideas and he suggested I send out and email about some of the possibilities. Since none of the ones I could think of are pretty he thought maybe you would have some thoughts or suggestions.

First a little back ground, the way darwin’s otool(1) does this is that it creates an llvm disassembler for both arm and thumb when disassembling a binary with 32-bit ARM cpu. It uses the C API in <llvm-c/Disassembler.h> and calls LLVMCreateDisasmCPU() twice, once with an arm TripleName and once with a matching thumb TripleName. Then for each 32-bit ARM cpu it will default to one or the other disassembler. Then as it disassembles and finds a symbol in the symbol table for the current PC being disassembled it will see of the symbol has the N_ARM_THUMB_DEF bit set or not. And then switch disassemblers between the arm and thumb disassemblers. While this is a bit of a hack there are a limited set of Mach-O cpus otool(1) deals with.

For llvm-objdump, it eventually just calls TheTarget->createMCDisassembler() and gets one disassembler for TheTarget it created.

I talked to Jim a bit about sinking the logic of maintaining multiple disassemblers down into the core disassembler logic and using subtarget to select between them. Like the ARMAsmParser and I think the ARMInstPrinter work. But that seems very complicated for a single target that has two disassemblers.

The implementation of llvm-objdump does have a MachODump.cpp for use with the -m option that I could do the a similar hack otool(1) like hack and special case 32-bit ARM cpus. And at least it contains the ugliness. But this does not really help the non -m case and I suspect ELF objects may face a similar problem.

The other more radical change I was thinking of was maybe changing MachODump.cpp to use the C API. Then at least this way we would have something in the tree that used this and could actually have test cases. That could then use the call backs to symbolic operands etc. But that still could be done with the C++ API using TheTarget->createMCSymbolizer() anyway.

So if any of you have suggestions for a direction for this let me know,
Kev

Hi Kevin,

I guess it depends on how many other targets need to deal with the
same problem, and how much their maintainers want to cope with the
change on their side.

Creating multiple disassemblers is wasteful, but not critical to tools
like objdump, that are rarely on the hot path. It would be
simpler/quicker to instantiate them on objdump and then, based on the
Thumb bit, it chooses one or the other. However, I think this would
not be the best solution for some reasons:

1. This is disassembler logic, and having objdump doing this on a
higher level means that other tools that (eventually) need the same
functionality will have to re-implement.
2. It shouldn't be that hard to join the ARM and Thumb disassembler,
given that they're on the same file, share most of the static
functions and could easily delegate with getInstruction() deciding
which to use: getThumbInstruction() or getARMInstruction() and
renaming Thumb/ARMDisassembler functions to match.

Though, I haven't got my hands on the disassembler that much, so other
people with more experience in that area could chime in and give more
reasons on either side.

My personal opinion is that it'd be more elegant and stable that way,
and any work we have to do now would compensate in the future, but
other back-end maintainers could disagree.

cheers,
--renato

Hi Renato,

Thanks for your reply. A few comments in line below.

Kev

First a little back ground, the way darwin’s otool(1) does this is that it creates an llvm disassembler for both arm and thumb when disassembling a binary with 32-bit ARM cpu. It uses the C API in <llvm-c/Disassembler.h> and calls LLVMCreateDisasmCPU() twice, once with an arm TripleName and once with a matching thumb TripleName. Then for each 32-bit ARM cpu it will default to one or the other disassembler. Then as it disassembles and finds a symbol in the symbol table for the current PC being disassembled it will see of the symbol has the N_ARM_THUMB_DEF bit set or not. And then switch disassemblers between the arm and thumb disassemblers. While this is a bit of a hack there are a limited set of Mach-O cpus otool(1) deals with.

Hi Kevin,

I guess it depends on how many other targets need to deal with the
same problem, and how much their maintainers want to cope with the
change on their side.

That’s the rub. I think only 32-bit arm has this issue with multiple disassemblers
and I would hate to add a bunch of stuff that all targets would have to deal with.
Love to hear if any other target maintainer could even uses this.

Creating multiple disassemblers is wasteful, but not critical to tools
like objdump, that are rarely on the hot path. It would be
simpler/quicker to instantiate them on objdump and then, based on the
Thumb bit, it chooses one or the other.

I agree with all that. And this would be pretty simple to deal with inside
objdump and be done with it.

However, I think this would
not be the best solution for some reasons:

1. This is disassembler logic, and having objdump doing this on a
higher level means that other tools that (eventually) need the same
functionality will have to re-implement.

Also agreed. But can you or anyone else think of other tools that would
need this logic? Hate to do a whole bunch of work adding to the lower
layers just to make objdump a bit cleaner.

2. It shouldn't be that hard to join the ARM and Thumb disassembler,
given that they're on the same file, share most of the static
functions and could easily delegate with getInstruction() deciding
which to use: getThumbInstruction() or getARMInstruction() and
renaming Thumb/ARMDisassembler functions to match.

Yep could do that. But it seems like a lot of work for very little pay off.

Though, I haven't got my hands on the disassembler that much, so other
people with more experience in that area could chime in and give more
reasons on either side.

Me too.

My personal opinion is that it'd be more elegant and stable that way,

Absolutely agree about this being more elegant.

and any work we have to do now would compensate in the future, but
other back-end maintainers could disagree.

As a maintainer of darwin’s otool(1) for some 20 plus years and plugging in
some 9 or more different disassemblers the use if two llvm disassembler’s for
32-bit arm is no big deal at all. Heck it still has the old arm disassemblers in
it and many other old ones and it is very stable and I rarely if ever have to touch
those interfaces.

Also agreed. But can you or anyone else think of other tools that would
need this logic? Hate to do a whole bunch of work adding to the lower
layers just to make objdump a bit cleaner.

Mainly lldb, I guess, which probably has to do some sort of magic, too.

As a maintainer of darwin’s otool(1) for some 20 plus years and plugging in
some 9 or more different disassemblers the use if two llvm disassembler’s for
32-bit arm is no big deal at all. Heck it still has the old arm disassemblers in
it and many other old ones and it is very stable and I rarely if ever have to touch
those interfaces.

I don't think it's that much uglier to use multiple disassemblers for
the same target when you already use for different targets, and as you
said, you already have a way to use a collection of them on demand in
otool, so that wouldn't be much of a new hack.

But I can't opine on objdump's design decisions. All I can say is that
I don't think the changes in the ARM back-end wouldn't be that
problematic if you decide to merge the disassemblers.

cheers,
--renato

The implementation of llvm-objdump does have a MachODump.cpp for use with the -m option that I could do the a similar hack otool(1) like hack and special case 32-bit ARM cpus. And at least it contains the ugliness. But this does not really help the non -m case and I suspect ELF objects may face a similar problem.

My gut feeling would be to do this first and keep an eye for how to
refactor it when we want to add support for ELF or for things like
mips16.

CCing Eric since he is now the expert on the target/subtarget relationship.

The other more radical change I was thinking of was maybe changing MachODump.cpp to use the C API. Then at least this way we would have something in the tree that used this and could actually have test cases. That could then use the call backs to symbolic operands etc. But that still could be done with the C++ API using TheTarget->createMCSymbolizer() anyway.

Using the C api seems odd.

So if any of you have suggestions for a direction for this let me know,
Kev

Cheers,
Rafael

Sure. Probably the easiest way would be to take all of the disparate
classes and throw them under the MCSubtargetInfo as best as can be
done, then you can just create a single object for each target you
want to disassemble for in the binary. arm/thumb mips/mips16 might be
a bit more difficult as you'll need to swap on a function by function
basis, but as long as you've got an index of subtargets to handle
disassembly it should be possible. Right now the arm/thumb interface
is a bit wonky there, but I think mips/mips16 should work - at least
it does for code generation, I haven't looked heavily at the
disassembler in a while.

If you're interested in going down this path though I'll give it some
thought and see what I can do.

-eric

Wouldn’t you hit a similar problem in x86 if you were disassembling a section on code that switched from 16 bit real mode to 32 bit or 64 bit mode?

Good possibility.

-eric