This is an archive of the discontinued LLVM Phabricator instance.

[compiler-rt] atos and dladdr symbolizers for OS X
ClosedPublic

Authored by kubamracek on Dec 9 2014, 7:07 PM.

Details

Reviewers
glider
samsonov
Summary

Hi everyone,

based on the discussion at https://groups.google.com/d/topic/address-sanitizer/2UaT7rvhvJ4/discussion about different symbolizers, especially on OS X, this patch implements a "fallback" symbolizer for OS X that uses the atos command line tool, which already ships with OS X. The main reason for that is to have easier deployment to machines that don't have llvm-symbolizer. Even though it was pointed out that atos may not be as accurate, having a backup symbolizer is still useful for issue suppressions, where one of the suppression types rely on having a working symbolizer.

As also discussed, the "real" long-term solution is to have internal_symbolizer (llvm-symbolizer built as a standalone static library) being built as part of the LLVM build, but that requires a significant amount of work. This atos patch is mostly meant to have something that works in the meantime.

I tried to keep the changes to existing code in sanitizer_symbolizer_posix_libcdep.cc minimal, so I reused the POSIXSymbolizer and added the AtosSymbolizer as another variant of an external symbolizer (a subclass of ExternalSymbolizerInterface). It would maybe make more sense to subclass SymbolizerProcess but that would require more refactoring. The POSIXSymbolizer class is also already very tied to how llvm-symbolizer works, and probably should also be refactored to allow using another tool/format.

That being said, this patch may be sub-optimal from the refactoring point of view, and I'll be glad to redo it properly, but first I'd like to ask if I'm on the right track or if this should be implemented in a completely different way.

Thanks,
Kuba

Diff Detail

Event Timeline

kubamracek updated this revision to Diff 17110.Dec 9 2014, 7:07 PM
kubamracek retitled this revision from to [compiler-rt] atos symbolizer for OS X.
kubamracek updated this object.
kubamracek edited the test plan for this revision. (Show Details)
kubamracek added a reviewer: glider.
kubamracek added a subscriber: Unknown Object (MLST).
kubamracek added subscribers: samsonov, kcc, glider.
samsonov edited edge metadata.Dec 15 2014, 10:53 AM

Once again, sorry for delay. Will take a look at it this week.

glider added inline comments.Dec 17 2014, 1:14 AM
lib/sanitizer_common/sanitizer_symbolizer_mac.cc
25

Please consider using fd_t for |fd_to_child_| and kInvalidFd for invalid fd.

32

This blew my mind out ;)
I'm curious whether using the same fd to write commands to the symbolizer and read the output actually works.
Is it in any sense better than having ye olde pipes or a socketpair like in StartSymbolizerSubprocess() in sanitizer_symbolizer_posix_libcdep.cc?

Anyway, I think we need to use the same code to spawn the subprocesses for both the addr2line and atos symbolizers. Not sure which version is better.

34

s/Fork/fork

35

I think you need to ensure fd isn't spoiled by the failed fork().

57

How about moving ExtractToken to a common file?
There's a similar function in sanitizer_symbolizer_posix_libcdep.cc

104

I've mixed feelings about internal_read() returning a uptr, but don't think we need to clean it up in this CL.

lib/sanitizer_common/sanitizer_symbolizer_mac.h
11

This is not accurate anymore, because we've more than two tools (although only ASan supports OSX so far)
How about "This file is shared between sanitizer tools run-time libraries"?

28

Please fix the include order (standard headers should be sorted alphabetically)

kubamracek added inline comments.Dec 17 2014, 12:55 PM
lib/sanitizer_common/sanitizer_symbolizer_mac.cc
32

The reason to use forkpty is because atos doesn't flush its output fd after it gives a response. So using regular pipes doesn't work here, because the response gets buffered within the C library. To disable this, we make a new pseudo-terminal which makes all output from atos unbuffered. This is not a problem for llvm-symbolizer, because it does a outs().flush() after each response.

samsonov added inline comments.Dec 17 2014, 6:54 PM
lib/sanitizer_common/sanitizer_symbolizer.h
144 ↗(On Diff #17110)

Looks like some overrides need one half of the arguments (module_name/module_offset), and some need another (addr). This kind of interface is not really convenient.

149 ↗(On Diff #17110)

This function has nothing to do with ExternalSymbolizerInterface

lib/sanitizer_common/sanitizer_symbolizer_mac.cc
95

Note: you need to use internal_iserror() for write/read syscalls. If we don't have them for existing code - this is most likely a bug.

lib/sanitizer_common/sanitizer_symbolizer_mac.h
22

Why do you need it?

34

don't you need to initialize fd_to_child_ here?

lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cc
692 ↗(On Diff #17110)

I don't like the way the code is structured: first you call SendCommand(), that looks at which symbolizers are available, and dispatches SendCommand() to one of them, and then you call ParseSymbolizedStackFrame() that does the similar dispatch. I don't believe this is correct. On top of that, there is libbacktrace_symbolizer_, which works in yet different way.

792 ↗(On Diff #17110)

I'd prefer having this under

if (SANITIZER_MAC)

, and all the AtosSymbolizer code not #ifdef'ed out on Linux - this way it would be easier to check that we don't break one platform while modifying the code for another.

kubamracek updated this revision to Diff 17698.Dec 29 2014, 4:46 PM
kubamracek edited edge metadata.

Updating the patch to address review comments and refactor the symbolizers interface a little bit more:

SymbolizerInterface becomes a generic interface that all the symbolizers implement, and has these methods:

  • bool SymbolizePC(uptr addr, SymbolizedStack *stack);
  • bool SymbolizeData(uptr addr, DataInfo *info)

SymbolizerProcess on the other hand is now a completely separate class that handles the communication with the external process. It no longer deals with modules names and offsets, it just receives a "command" and provides a "response". The new LLVMSymbolizer class is responsible for constructing the string command for LLVMSymbolizerProcess and parsing the response back into SymbolizedStack or DataInfo. This way, I can implement AtosSymbolizerProcess as a very simple subclass of SymbolizerProcess.

I changed the StartSymbolizerSubprocess() method to use the forkpty call (instead of fork, sock_pair and pipe) for all subprocesses. It's needed for atos, in order to disable buffering in the new terminal (otherwise the response gets buffered inside libc and never returned until the input stream is closed). This also simplifies this method a lot, and we don't need to handle the case when stdin/stdout/stderr are closed.

The patch also adds one more symbolizer, DlAddrSymbolizer, which is extremely simple, and just calls dladdr() to retrieve a symbol name, and doesn't provide any file names or line numbers. It's used as a fallback when spawning an external symbolizer fails (e.g. because we're in a no-fork-allowed sandbox).

I added some new test cases that show that we can still provide symbol names in a no-fork sandbox, and that suppressions specified by a symbol name also work.

glider added inline comments.Dec 30 2014, 7:04 AM
lib/sanitizer_common/CMakeLists.txt
32

Please swap this line with the previous one.

lib/sanitizer_common/sanitizer_common.cc
293 ↗(On Diff #17698)

As far as I can tell, both ExtractInt and ExtractUptr are used to extract unsigned integers. Why use both (and deal with the potential overflow situations)?

315 ↗(On Diff #17698)

How 'bout "ExtractTokenUpToDelimiter" since this function is extracting tokens, not ints or uptrs?

325 ↗(On Diff #17698)

Isn't that just prefix_len?

lib/sanitizer_common/sanitizer_common.h
71 ↗(On Diff #17698)

This group of helpers needs at least a brief comment.

lib/sanitizer_common/sanitizer_symbolizer_mac.cc
30

In this case you're assigning "0xdeadbeef" to res->info.function, is that intentional?

lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cc
170 ↗(On Diff #17698)

A comment may give better understanding of what you're parsing here.

205 ↗(On Diff #17698)

Is the column part always present? I think for addr2line it is not. In that case, will info->column be initialized properly?

221 ↗(On Diff #17698)

I think this function (and the other one) has little to do with POSIX, despite it's in a *_posix_*.cc file.
Better remove "POSIX" from the name.

226 ↗(On Diff #17698)

Two spaces before the comment, please (here and in the tests below)

test/asan/TestCases/closed-fds.cc
2 ↗(On Diff #17698)

Wonder if we want/should check that exactly the required symbolizer is being used (e.g. print its name under verbosity=2)

kubamracek updated this revision to Diff 17728.Dec 30 2014, 2:08 PM

Addressing review comments.

As far as I can tell, both ExtractInt and ExtractUptr are used to extract unsigned integers. Why use both (and deal with the potential overflow situations)?

Removed ExtractInt and kept only ExtractUptr.

prefix_end += internal_strlen(delimiter);

Isn't that just prefix_len?

No, this line moves the pointer *after* the delimiter (if one was found), I think we really need strlen(delimiter) here.

Wonder if we want/should check that exactly the required symbolizer is being used (e.g. print its name under verbosity=2)

Good idea. Added.

If you need to refactor some interfaces or move code around in order to implement Mac-specific symbolizers, let's start with small patches doing that, which introduce no behavior changes.

lib/sanitizer_common/sanitizer_common.h
75 ↗(On Diff #17728)

I'd prefer to have these function in sanitizer_symbolizer.(h|cc), as for now they are not used anywhere else.

lib/sanitizer_common/sanitizer_symbolizer.h
145 ↗(On Diff #17728)

I don't like that in some concrete classes (e.g. LibbacktraceSymbolizer) require that certain fields of "stack" structure are filled by the caller.

154 ↗(On Diff #17728)

This buffer has nothing to do with the interface, it is an implementation detail of specific concrete classes.

168 ↗(On Diff #17728)

Please move implementation to .cc files.

lib/sanitizer_common/sanitizer_symbolizer_libbacktrace.cc
159 ↗(On Diff #17728)

This function is completely wrong. First of all, it should return bool now, not SymbolizedStack*. Then, "SymbolizedStack*" argument you pass to this function is leaked, so is its module_name. It seems that the "stack" argument is only required to pass in module_name/module_offset, and serves no actual purpose by itself. It means that the interface is broken.

lib/sanitizer_common/sanitizer_symbolizer_libbacktrace.h
35 ↗(On Diff #17728)

add override keywords for method overloads please.

lib/sanitizer_common/sanitizer_symbolizer_mac.h
47

"new"?!

No, please don't call system allocator in the symbolizer code.

lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cc
28 ↗(On Diff #17728)

Does this header exist on all POSIX systems?

33 ↗(On Diff #17728)

Does this header exist on all POSIX systems?

509 ↗(On Diff #17728)

dladdr may not be available on some platforms. I'd suggest to use it on Mac only for now.

620 ↗(On Diff #17728)

Wait, LibbacktraceSymbolizer and DladdrSymbolizer also implement SymbolizerInterface, why don't we return them here?

699 ↗(On Diff #17728)

We have VReport for these purposes.

Thanks for the review, I'll extract a NFC refactoring patch, but first I'd like to ask you to clarify what you suggest in the following comments:

I don't like that in some concrete classes (e.g. LibbacktraceSymbolizer) require that certain fields of "stack" structure are filled by the caller.

I see. It's meant as both an input and output, and since it's created at a single place (in POSIXSymbolizer::SymbolizePC), it's always pre-filled with the address, module name and module offset. I did that to avoid passing these 3 values to all the function calls. Any better suggestions what the interface should look like?

Wait, LibbacktraceSymbolizer and DladdrSymbolizer also implement SymbolizerInterface, why don't we return them here?

I wanted to keep the current behavior, which is a weird chain: On every symbolication request, LibbacktraceSymbolizer is called first, and if it fails, we try the "regular" symbolizer, which is either internal_symbolizer or external_symbolizer. I also wanted to add the DladdrSymbolizer into that chain, so it's only used when a "regular" symbolizer failed. The reason is that we only realize that we cannot spawn an external symbolizer after we try to at least once.

You're right that the change to LibbacktraceSymbolizer was completely broken, sorry was that.

kubamracek updated this revision to Diff 20595.Feb 24 2015, 7:15 AM
kubamracek retitled this revision from [compiler-rt] atos symbolizer for OS X to [compiler-rt] atos and dladdr symbolizers for OS X.

A refactoring patch (http://reviews.llvm.org/D7827) is now extracted. Updating this patch to be an implementation of AtosSymbolizer and DlAddrSymbolizer on top of the refactorings.

kubamracek updated this revision to Diff 21157.Mar 3 2015, 4:42 PM

Looks like the symbolizer refactoring is making some progress, so I'm updating this patch (that adds AtosSymbolizer and DlAddrSymbolizer) so it can be applied cleanly. I'm not adding these new symbolizer tools into the chain yet, but having them committed would be convenient for me. Alexey, what do you think? Or should I first finish all the refactoring?

samsonov added inline comments.Mar 10 2015, 12:28 PM
lib/sanitizer_common/sanitizer_symbolizer_mac.cc
58

I don't like that this function essentially copies so much of the base class code. Do you have a good understanding of why we need to use one code to run llvm-symbolizer on Mac and different code (forkpty etc.) to run atos on Mac? Can we instead make certain bits of StartSymbolizerProcess() platform-specific?

67

Why do you need this?

116

no need to fill with zeroes, snprintf should do the right thing.

126

static?

138

factor out these constants and check in a helper function.

170

Wait, shouldn't AtosSymbolizerProcess be created via InternalAllocator?

178

Why do you need this here? If you want to avoid printing multiple error messages, you can just drop the reference to process_

188

See above - I don't think we need to add *any* implementation for non-Mac platforms.

lib/sanitizer_common/sanitizer_symbolizer_mac.h
2

Add this file to CMakeLists.txt

19

Do you really need the declaration of DlAddrSymbolizer and AtosSymbolizer on non-Mac? Maybe, we could instead protect the whole .h and .cc file with

#if SANITIZER_MAC

directive? E.g. you only provide AtosSymbolizerProcess under SANITIZER_MAC anyway.

Do you have a good understanding of why we need to use one code to run llvm-symbolizer on Mac and different code (forkpty etc.) to run atos on Mac? Can we instead make certain bits of StartSymbolizerProcess() platform-specific?

The reason to use forkpty is because atos doesn't flush its output fd after it gives a response. So using regular pipes doesn't work here, because the response gets buffered within the C library. To disable this, we make a new pseudo-terminal which makes all output from atos unbuffered. This is not a problem for llvm-symbolizer, because it does a outs().flush() after each response.

kubamracek updated this revision to Diff 21725.Mar 11 2015, 8:31 AM

Addressing review comments. Moved the forkpty part into SymbolizerProcess under a use_forkpty option. Protected the .h and .cc files with #if SANITIZER_MAC.

samsonov accepted this revision.Mar 11 2015, 11:54 AM
samsonov edited edge metadata.

LGTM after addressing comments below. Thanks!

lib/sanitizer_common/sanitizer_symbolizer_mac.cc
74

I believe we have ARRAY_SIZE macro for that.

97

InternalFree(trim);

131

You can remove extra temp variable:

if (!ParseCommandOutput(buf, stack)) {
  process_ = nullptr;
  return false;
}
return true;

Add a comment describing why you discard the process in this case.

This revision is now accepted and ready to land.Mar 11 2015, 11:54 AM
kubamracek closed this revision.Mar 12 2015, 3:56 AM

Landed in r232026.