Page MenuHomePhabricator

Add an OperatingSystem plugin to support goroutines
ClosedPublic

Authored by ribrdb on Oct 20 2014, 12:32 PM.

Diff Detail

Repository
rL LLVM

Event Timeline

ribrdb updated this revision to Diff 15150.Oct 20 2014, 12:32 PM
ribrdb retitled this revision from to Add an OperatingSystem plugin to support goroutines.
ribrdb updated this object.
ribrdb edited the test plan for this revision. (Show Details)
ribrdb added a subscriber: Unknown Object (MLST).

Greg should comment on way you're populating the Memory threads, etc. That was his code (used mostly by the OS X kernel folks to turn kernel thread activations into lldb threads...) At present you can only enable/disable the OS plugins when the target is created, but I haven't looked into how hard that would be to change.

But anyway most of the extensions (like the JIT loader and the ASAN plugin) do their initialization work by inserting some callback in:

Process::ModulesDidLoad

which gets called every time a new shared library is loaded. You can just check the module list you were passed and if it contains the symbol you are looking for, run your Init method. At some point we should create some formal process where plugins can register "new module loaded" handlers rather than just jamming them in here by hand. If you are feeling ambitious, have a go! Otherwise, just do what everybody else does...

Jim

ribrdb updated this revision to Diff 34806.Sep 15 2015, 9:21 AM
ribrdb updated this object.
ribrdb set the repository for this revision to rL LLVM.

I've updated this to load the plugin when modules are loaded, added a setting to enable/disable the goroutine plugin, and added a test.

tberghammer requested changes to this revision.Sep 15 2015, 9:43 AM
tberghammer added a reviewer: tberghammer.
tberghammer added a subscriber: tberghammer.

If I understand this change correctly then OperatingSystemGo::CreateInstance will be called every time we launch or attach a new process. This function then calls OperatingSystemGo::FindGlobal what will call into ModuleList::FindGlobalVariables. The problem is that it will require a full dwarf info parsing for all module what is very slow and we would like to avoid it at almost all cost. The rest of the plugins mentioned by Jim (JIT, ASAN) are doing only a symbol name lookup based on a fixed symbol what is significantly faster because it requires only the parsing of the symtab. Please try to find a way to detect when you have to enable the go plugin based on symtab only or based on the ELF headers what can be parsed even faster (they contain a language attribute what should be sufficient (if it is filled in) on Linux/OSX, but I don't know about Windows).

Note: If CreateInstace isn't called at launch/attach time when we are working with a non-go inferior, then feel free to ignore my comment.

This revision now requires changes to proceed.Sep 15 2015, 9:43 AM
ribrdb updated this revision to Diff 34813.Sep 15 2015, 11:18 AM
ribrdb edited edge metadata.

Ok, I've changed it to use the symbol table like the ASAN runtime does.
I'm not seeing any language info in the elf or macho file headers.
It looks like there's also a gosymtab section I could look for, but the name is different in macho and elf so I'd prefer this one.

ribrdb updated this revision to Diff 34839.Sep 15 2015, 2:40 PM
ribrdb edited edge metadata.

Fix cmake build.

clayborg requested changes to this revision.Sep 15 2015, 2:49 PM
clayborg edited edge metadata.

We have the notion of hard coded section types. See lldb::SectionType in lldb-enumerations.h. You could always add your Go section to this section list and then find the section by section type:

const SectionList *section_list = module_sp->GetSectionList();
if (section_list)
{
    SectionSP section_sp (section_list->FindSectionByType(eSectionTypeGoSymtab, true));
    if (section_sp)
    {

Then you would need to modify ObjectFileMachO and ObjectFileELF to recognize the section and set the section type correctly...

source/Core/PluginManager.cpp
774–778 ↗(On Diff #34813)

Please leave this in the previous style. convert to using nullptr, and put spaces before the parens:

OperatingSystemInstance() :
    name (),
    description (),
    create_callback (nullptr),
    debugger_init_callback (nullptr),
source/Plugins/OperatingSystem/Go/OperatingSystemGo.cpp
153–162 ↗(On Diff #34813)

Convert over to using eSectionTypeGoSymtab as mentioned in main comment?

source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
2047–2050 ↗(On Diff #34813)

I am confused as to why we need to do this. If you have a memory thread, it might be backed by a real thread, or it might not. If it is backed by a real thread, we should be asking the real thread why it really stopped and the memory thread will say that it stopped for the same reason the real thread stopped. If it isn't backed by a real thread, then the memory thread will say why it stopped. Can you elaborate on why you think you need to do this?

source/Target/ThreadList.cpp
776–777 ↗(On Diff #34813)

Why is this needed? Do you have a case where sometimes you have a real thread that isn't backed by memory thread, and later it does become backed by a memory thread or vice versa?

This revision now requires changes to proceed.Sep 15 2015, 2:49 PM

Moving to SectionType is even more efficient than searching for symbols by name, so please switch to that.

brucem added a subscriber: brucem.Sep 16 2015, 1:55 AM
brucem added inline comments.
test/lang/go/goroutines/TestGoroutines.py
14 ↗(On Diff #34839)

Minor typo: should be "suite"

ribrdb updated this revision to Diff 34914.Sep 16 2015, 12:37 PM
ribrdb edited edge metadata.

I am confused as to why we need to do this. If you have a memory thread, it
might be backed by a real thread, or it might not. If it is backed by a
real thread, we should be asking the real thread why it really stopped and
the memory thread will say that it stopped for the same reason the real
thread stopped. If it isn't backed by a real thread, then the memory thread
will say why it stopped. Can you elaborate on why you think you need to do
this?

Hmm. Actually I'm not sure this is necessary anymore -- the unittest seems to pass without it.
Originally I added this because we were occasionally having problems where you'd do 'next',
but lldb would never stop.
This was a long time ago so things might have changed, but I think this is what was going on:

  • StepOver set some sort of thread specific breakpoint for the MemoryThread's tid (e.g. 1)
  • When you hit the breakpoint the Process only checked against the real thread (e.g. tid 31984)
  • The tids don't match so it ignores the breakpoint. Or sometimes stops with a strange reason.

Even with this I think there's still some problems with thread plans and memory threads.
I did a little testing with go and things seem to be working. But I tried creating a couple python
plugins (one that alternates between a core backed thread and no memory threads, and one with 2 memory threads that alternate between 1 backing thread). In both cases if I used "next" lldb stopped with a strange reason.

Why is this needed? Do you have a case where sometimes you have a real
thread that isn't backed by memory thread, and later it does become backed
by a memory thread or vice versa?

Yes the go runtime creates some number of real "M" threads, and schedules
user level "g" threads across the "M"s. A "g" may move around between "M"s.
Also it's common that all the "g"s are blocked on io or locks, so an "M" is
just sitting around waiting for a "g" to unblock.

I suppose it also would be possible to stop while an "M" is switching
between "g"s. In either case there would be no memory thread at that moment.

ribrdb marked 2 inline comments as done.Sep 16 2015, 12:38 PM
clayborg accepted this revision.Sep 16 2015, 12:49 PM
clayborg edited edge metadata.

Looks good then. I know there are things that are wrong with the current OS plug-ins, so lets just work on improving them to be as useful as possible as I am sure there will be things that need fixing.

Eventual goals could be to allow stepping on memory threads. This is currently not allowed. If a memory thread isn't backed by an actual thread, we could set a thread specific breakpoint at the PC of the memory thread so when it does resume it will stop. The breakpoint action could then cause it to actually make the step happen once it is backed by a real thread. We would need to watch for this thread getting context switched out and pause the step if we need to.

This revision was automatically updated to reflect the committed changes.