This is an archive of the discontinued LLVM Phabricator instance.

Symbolizer - Teach symbolizer to work directly on object file.
ClosedPublic

Authored by pvellien on Jan 22 2021, 6:45 AM.

Details

Summary

This patch intended to provide additional interface to LLVMsymbolizer such that they work directly on object files. There is an existing method - symbolizecode which takes an object file, this patch provides similar overloads for symbolizeInlinedCode, symbolizeData, symbolizeFrame. This can be useful for clients who already have a in-memory object files to symbolize for.

Diff Detail

Event Timeline

pvellien created this revision.Jan 22 2021, 6:45 AM
pvellien requested review of this revision.Jan 22 2021, 6:45 AM
Herald added a project: Restricted Project. · View Herald TranscriptJan 22 2021, 6:45 AM
scott.linder added inline comments.Jan 25 2021, 3:44 PM
llvm/lib/DebugInfo/Symbolize/Symbolize.cpp
73–74

See below, it seems like this is just a bug currently?

579–602

The logic for CoffObject is not repeated elsewhere, which doesn't seem intended?

It seems like this method should also be overloaded, i.e. rather than repeat the "try to find it, if not create it" logic in each overload above, just implement LLVMSymbolizer::getOrCreateModuleInfo(const ObjectFile &) too, and the bodies elsewhere should become identical. I'd also consider just making the private *Common methods templates over the type of the first argument, and have the overloads be one-line wrappers:

template <typename T>
Expected<DILineInfo> symbolizeCommonCode(T ModuleSpecifier, object::SectionedAddress ModuleOffset) {
    SymbolizableModule *Info;
    if (auto InfoOrErr = getOrCreateModuleInfo(ModuleSpecifier))
      Info = InfoOrErr.get();
    else
      return InfoOrErr.takeError();
    // ... the existing body in terms of Info and ModuleOffset
}
Expected<DILineInfo> LLVMSymbolizer::symbolizeCode(const std::string &ModuleName, object::SectionedAddress ModuleOffset) { return symbolizeCodeCommon(ModuleName, ModuleOffset); }
Expected<DILineInfo> LLVMSymbolizer::symbolizeCode(const ObjectFile &ModuleObjectFile, object::SectionedAddress ModuleOffset) { return symbolizeCodeCommon(ModuleObjectFile, ModuleOffset); }

I think this minimizes the amount of boilerplate, short of using the preprocessor, while still presenting the nice overloaded interface rather than the templated one.

scott.linder added inline comments.Jan 25 2021, 3:55 PM
llvm/lib/DebugInfo/Symbolize/Symbolize.cpp
579–602

Small amendment: I'd change the snippet above slightly to begin the method body with:

auto InfoOrErr = getOrCreateModuleInfo(ModuleSpecifier);
if (auto Err = InfoOrErr.takeError())
  return Err;
SymbolizableModule *Info = *InfoOrErr;

Which is shorter, follows the convention of most of the rest of the file, and follows https://llvm.org/docs/ProgrammersManual.html#recoverable-errors

pvellien updated this revision to Diff 319620.Jan 27 2021, 10:43 AM
pvellien added a reviewer: MaskRay.

Refactor code

scott.linder edited reviewers, added: t-tye; removed: tony-tye.Feb 1 2021, 11:18 AM

This LGTM now, but I would give @ychen and @MaskRay time to take a look.

I'm also curious where the TODO discussed at https://reviews.llvm.org/D63521?id=206255#inline-568633 went, and if it was intentional?

llvm/lib/DebugInfo/Symbolize/Symbolize.cpp
614

Could you add a FIXME: comment here to the effect of "Should this handle COFF specially like getOrCreateModuleInfo(const std::string &) does?"

pvellien updated this revision to Diff 321318.Feb 3 2021, 10:54 PM

Added FIXME

scott.linder accepted this revision.Feb 12 2021, 10:24 AM

I'm accepting this as there hasn't been any response from others and I don't know who else might be interested, but if anyone has concerns please feel free to review post-commit, and we can revert as necessary.

This revision is now accepted and ready to land.Feb 12 2021, 10:24 AM
This revision was landed with ongoing or failed builds.Feb 12 2021, 10:30 AM
This revision was automatically updated to reflect the committed changes.

Generally there should be test coverage - otherwise these APIs are likely to be cleaned up as unused, even if the chance of actual bugs in them is small (since they're just simple wrappers).

Perhaps some unit testing would be suitable?

Generally there should be test coverage - otherwise these APIs are likely to be cleaned up as unused, even if the chance of actual bugs in them is small (since they're just simple wrappers).

Perhaps some unit testing would be suitable?

Hi dave, thanks for your comments. I was worried about this thing. But there are no existing tools in llvm that work on object file directly expect there is a usage for one existing API - symbolizeCode. I don't know how it is reliable to add unit tests, I would highly appreciate your thoughts on this. thanks

Generally there should be test coverage - otherwise these APIs are likely to be cleaned up as unused, even if the chance of actual bugs in them is small (since they're just simple wrappers).

Perhaps some unit testing would be suitable?

Hi dave, thanks for your comments. I was worried about this thing. But there are no existing tools in llvm that work on object file directly expect there is a usage for one existing API - symbolizeCode.

Sorry, I'm not quite following this last sentence, could you rephrase it?

The first part (" But there are no existing tools in llvm that work on object file directly ") doesn't seem quite right, so far as I'm reading it - llvm-symbolizer, the command line tool, can operate on object files and many of the llvm-symbolizer tests operate on object files. (which also makes me wonder about this change in general - llvm-symbolizer can already work on object files, so why are new APIs required, given the functionality already exists/is used/tested?)

I don't know how it is reliable to add unit tests, I would highly appreciate your thoughts on this. thanks

Generally there should be test coverage - otherwise these APIs are likely to be cleaned up as unused, even if the chance of actual bugs in them is small (since they're just simple wrappers).

Perhaps some unit testing would be suitable?

Hi dave, thanks for your comments. I was worried about this thing. But there are no existing tools in llvm that work on object file directly expect there is a usage for one existing API - symbolizeCode.

Sorry, I'm not quite following this last sentence, could you rephrase it?

The first part (" But there are no existing tools in llvm that work on object file directly ") doesn't seem quite right, so far as I'm reading it - llvm-symbolizer, the command line tool, can operate on object files and many of the llvm-symbolizer tests operate on object files. (which also makes me wonder about this change in general - llvm-symbolizer can already work on object files, so why are new APIs required, given the functionality already exists/is used/tested?)

I don't know how it is reliable to add unit tests, I would highly appreciate your thoughts on this. thanks

Oh sorry, I mean an in-memory object file rather then a on-disk object file. This is similar to this patch : https://reviews.llvm.org/D63521 but it adds overloads for missing functions. Whether it helps? thanks

From my reads of the history of this component, I think DebugInfo/Symbolize was extracted from llvm-symbolizer because sanitizer runtime needs it. Later other LLVM internal tools (sanitizer runtime, sancov, sanstats, llvm-xray, etc) use the API as well.
I don't find usage from open-source projects. There could be, but I speculate that if do some refactoring the friction will be small.

Currently the std::string overloads are mainly used. It seems to me that we don't need to std::string overloads. They can likely all switch to the const ObjectFile & overloads.
I will take a stab at cleaning up the call sites.

@pvellien @scott.linder The concern with not testing the new API is that they are otherwise unused (I guess that you may have downstream projects which may adopt them soon) and may be deleted by other contributors as dead code.
If you have some specific use cases, contributing unittests would probably be a good idea.
I'll try refactoring the API, if const std::string& is replaced with const ObjectFile & overloads, then the API will be used by in-tree code and will be less likely deleted as dead code.

MaskRay added a comment.EditedFeb 16 2021, 11:02 AM

BTW: symbolizeData is special. Technically symbolizeData does not different things than symbolizeCode. The difference is that it returns DIGlobal which has the size information. Many older binary formats before ELF don't record size information. It is currently only used by hwasan and tsan for diagnostics (D96322).

addr2line does not have a "DATA" symbolization mode. I'd be really curious how you'd use that API.

Thanks for taking a look @MaskRay

MaskRay added a comment.EditedFeb 16 2021, 12:59 PM

(CC @jhenderson @rnk )

The issues with the const std::string & parameter symbolize* API:

  • The LLVMSymbolizer instance needs to construct ObjectFile, which may be a duplicate if the application has an existing ObjectFile instance.
  • The file reading error is buried in the API. The application cannot easily tell there is an IO error or incorrect address error and may keep symbolizing more addresses after an IO error. In addition, the user may want to handle the IO error themselves.
  • It does magic things (opening an auxiliary file) which may be of security concern for some usage: it constructs a .dsym path for a Mach-O file; it constructs a path from build ID for an ELF file; it constructs a .gnu_debuglink path if the section is present in an ELF file; it constructs a PDB path for a PE file. Personally I'd like the feature to open auxiliary files to be specified explicitly by the user on ELF.

The const ObjectFile & symbolize* does not have the ability to open magic auxiliary files. This is not clear from the API.

If we want to keep just one set of symbolize* overloads and go for const ObjectFile &, we perhaps need to store ObjectFile handles instead of StringRef handles for LLVMSymbolizer's internal maps, and make Expected<SymbolizableModule *> LLVMSymbolizer::getOrCreateModuleInfo(const std::string &ModuleName) public.

I want to check with folks whether whether some refactoring is needed in this area.


This can be useful for clients who already have a in-memory object files to symbolize for.

I'd hear more about the use case. For in-memory object files, how do you handle .gnu_debuglink and the build ID stuff. It is possible that you don't want to handle this.

I'm not sure if this is what @dblaikie was referring to, but it seems like a good way to test new public-facing APIs would be to write gtest unit tests. I don't think there's any precedent for this for the symbolizer API, so it may not be particularly easy. Refactoring llvm-symbolizer to make use of the new API is probably a good idea, and it may help with the test coverage, but there is a risk that a future refactor beyond that will cause llvm-symbolizer to no longer use the API, and therefore the test coverage is lost.

From my reads of the history of this component, I think DebugInfo/Symbolize was extracted from llvm-symbolizer because sanitizer runtime needs it. Later other LLVM internal tools (sanitizer runtime, sancov, sanstats, llvm-xray, etc) use the API as well.
I don't find usage from open-source projects. There could be, but I speculate that if do some refactoring the friction will be small.

Currently the std::string overloads are mainly used. It seems to me that we don't need to std::string overloads. They can likely all switch to the const ObjectFile & overloads.
I will take a stab at cleaning up the call sites.

@pvellien @scott.linder The concern with not testing the new API is that they are otherwise unused (I guess that you may have downstream projects which may adopt them soon) and may be deleted by other contributors as dead code.
If you have some specific use cases, contributing unittests would probably be a good idea.
I'll try refactoring the API, if const std::string& is replaced with const ObjectFile & overloads, then the API will be used by in-tree code and will be less likely deleted as dead code.

@MaskRay Yes, we planned to use those APIs for the heterogeneous address sanitizer, where we have a device (gpu) object code embedded in a data section of the executable. If we want to symbolize the device object code it would to good to have llvm-symbolizer to symbolize directly on the in-memory instance rather than dumping data to a file and passing the file name to llvm-symbolizer.

(CC @jhenderson @rnk )

The issues with the const std::string & parameter symbolize* API:

  • The LLVMSymbolizer instance needs to construct ObjectFile, which may be a duplicate if the application has an existing ObjectFile instance.
  • The file reading error is buried in the API. The application cannot easily tell there is an IO error or incorrect address error and may keep symbolizing more addresses after an IO error. In addition, the user may want to handle the IO error themselves.
  • It does magic things (opening an auxiliary file) which may be of security concern for some usage: it constructs a .dsym path for a Mach-O file; it constructs a path from build ID for an ELF file; it constructs a .gnu_debuglink path if the section is present in an ELF file; it constructs a PDB path for a PE file. Personally I'd like the feature to open auxiliary files to be specified explicitly by the user on ELF.

The const ObjectFile & symbolize* does not have the ability to open magic auxiliary files. This is not clear from the API.

If we want to keep just one set of symbolize* overloads and go for const ObjectFile &, we perhaps need to store ObjectFile handles instead of StringRef handles for LLVMSymbolizer's internal maps, and make Expected<SymbolizableModule *> LLVMSymbolizer::getOrCreateModuleInfo(const std::string &ModuleName) public.

I want to check with folks whether whether some refactoring is needed in this area.


This can be useful for clients who already have a in-memory object files to symbolize for.

I'd hear more about the use case. For in-memory object files, how do you handle .gnu_debuglink and the build ID stuff. It is possible that you don't want to handle this.

we are not handling split dwarf scenario.

I'm not sure if this is what @dblaikie was referring to, but it seems like a good way to test new public-facing APIs would be to write gtest unit tests. I don't think there's any precedent for this for the symbolizer API, so it may not be particularly easy. Refactoring llvm-symbolizer to make use of the new API is probably a good idea, and it may help with the test coverage, but there is a risk that a future refactor beyond that will cause llvm-symbolizer to no longer use the API, and therefore the test coverage is lost.

@jhenderson so if we about to write unit tests for new symbolizer APIs how one should proceed? I would like to get your thoughts since there is no unit tests for Symbolizer

(CC @jhenderson @rnk )

The issues with the const std::string & parameter symbolize* API:

  • The LLVMSymbolizer instance needs to construct ObjectFile, which may be a duplicate if the application has an existing ObjectFile instance.
  • The file reading error is buried in the API. The application cannot easily tell there is an IO error or incorrect address error and may keep symbolizing more addresses after an IO error. In addition, the user may want to handle the IO error themselves.
  • It does magic things (opening an auxiliary file) which may be of security concern for some usage: it constructs a .dsym path for a Mach-O file; it constructs a path from build ID for an ELF file; it constructs a .gnu_debuglink path if the section is present in an ELF file; it constructs a PDB path for a PE file. Personally I'd like the feature to open auxiliary files to be specified explicitly by the user on ELF.

These ^ are the reasons you're suggesting it would be good to refactor the existing code to take an ObjectFile instead of a std::string file name?

The const ObjectFile & symbolize* does not have the ability to open magic auxiliary files. This is not clear from the API.

This ^ is a limitation of these newly proposed ObjectFile APIs? So they won't be entirely compatible with the existing string-based APIs? So it would be difficult/not possible to refactor the existing code to use these APIs?

If we want to keep just one set of symbolize* overloads and go for const ObjectFile &, we perhaps need to store ObjectFile handles instead of StringRef handles for LLVMSymbolizer's internal maps, and make Expected<SymbolizableModule *> LLVMSymbolizer::getOrCreateModuleInfo(const std::string &ModuleName) public.

What's the current lifetime management of the StringRefs in these internal maps? If they come from multiple different external calls passing in strings, that seems like it'd have lifetime problems (might not be clear the caller needs to keep the underlying string data alive) & would have similar problems with ObjectFile lifetime semantics. But if that's the existing contract/complication, perhaps it's not causing any particular problems.

(CC @jhenderson @rnk )

The issues with the const std::string & parameter symbolize* API:

  • The LLVMSymbolizer instance needs to construct ObjectFile, which may be a duplicate if the application has an existing ObjectFile instance.
  • The file reading error is buried in the API. The application cannot easily tell there is an IO error or incorrect address error and may keep symbolizing more addresses after an IO error. In addition, the user may want to handle the IO error themselves.
  • It does magic things (opening an auxiliary file) which may be of security concern for some usage: it constructs a .dsym path for a Mach-O file; it constructs a path from build ID for an ELF file; it constructs a .gnu_debuglink path if the section is present in an ELF file; it constructs a PDB path for a PE file. Personally I'd like the feature to open auxiliary files to be specified explicitly by the user on ELF.

These ^ are the reasons you're suggesting it would be good to refactor the existing code to take an ObjectFile instead of a std::string file name?

Yes

The const ObjectFile & symbolize* does not have the ability to open magic auxiliary files. This is not clear from the API.

This ^ is a limitation of these newly proposed ObjectFile APIs? So they won't be entirely compatible with the existing string-based APIs? So it would be difficult/not possible to refactor the existing code to use these APIs?

I think we can pass the file with debuginfo as an additional parameter as suggested by @MaskRay regarding refactoring all the use-cases within llvm to new APIs I'm not sure how complicated it would be in tools such as symbolizer, objdump etc

If we want to keep just one set of symbolize* overloads and go for const ObjectFile &, we perhaps need to store ObjectFile handles instead of StringRef handles for LLVMSymbolizer's internal maps, and make Expected<SymbolizableModule *> LLVMSymbolizer::getOrCreateModuleInfo(const std::string &ModuleName) public.

What's the current lifetime management of the StringRefs in these internal maps? If they come from multiple different external calls passing in strings, that seems like it'd have lifetime problems (might not be clear the caller needs to keep the underlying string data alive) & would have similar problems with ObjectFile lifetime semantics. But if that's the existing contract/complication, perhaps it's not causing any particular problems.

I'm not sure if this is what @dblaikie was referring to, but it seems like a good way to test new public-facing APIs would be to write gtest unit tests. I don't think there's any precedent for this for the symbolizer API, so it may not be particularly easy. Refactoring llvm-symbolizer to make use of the new API is probably a good idea, and it may help with the test coverage, but there is a risk that a future refactor beyond that will cause llvm-symbolizer to no longer use the API, and therefore the test coverage is lost.

@jhenderson so if we about to write unit tests for new symbolizer APIs how one should proceed? I would like to get your thoughts since there is no unit tests for Symbolizer

Sorry for the delay - I've got a very big workload currently and haven't had a chance to get through all review comments from the past week or so. You'd need to write CMakeLists.txt and add a source file for the tests, much like there already is in DebugInfoDWARFTests. You'd then likely want to identify some method to generate test inputs with the required properties. The exact nature of this would depend on what exactly you want to test, but two options might be to reuse the DwarfGenerator code in the DWARF tests, or to use YAML inputs as some other tests for libObject and other places do, to create the object. I can't really give you any more precise details than that, I'm afraid, as I don't know exactly the best way forward without spending time attempting it myself, but I hope these give you some ideas to start with.