Modify a module at runtime in MCJIT

Hi Manoel,

CC’ing the dev list, since the answer may be relevant to others…

Hi Manoel,

CCing the Dev List.

Symbol resolution in Orc is handled by supplying a RuntimeDyld::SymbolResolver instance when you add modules to the JIT. When the module you’ve added to the JIT is linked, the SymbolResolver will be invoked to find any symbols not contained in the module itself.

The Kaleidoscope code that builds the SymbolResolver instance in Kaleidoscope/initial/toy.cpp file looks like this:

auto Resolver = createLambdaResolver(
[&](const std::string &Name) {
if (auto Sym = findSymbol(Name))
return RuntimeDyld::SymbolInfo(Sym.getAddress(),
Sym.getFlags());
return RuntimeDyld::SymbolInfo(nullptr);
},
(const std::string &S) { return nullptr; }
);
return CompileLayer.addModuleSet(singletonSet(std::move(M)),
make_unique(),
std::move(Resolver));

For the purposes of this discussion we can ignore the second lambda (it’s for “logical dylib lookup”, which is a feature you probably don’t need). The first lambda is the important one here, and it only tries to do one thing: resolve the requested symbol by looking elsewhere in the JIT (that’s the call to ‘findSymbol’).

To enable resolution of other symbols there are two straightforward approaches.

(1) Construct a map of the external symbols you want to make visible to the JIT. I prefer this approach where it’s practical. It minimizes the surface area between the JIT’d code and the host, which is good for security.

To use this approach, you just add a StringMap member to your JIT class (call it “ExternalSymbols”, or something like that). In your constructor you populate the map:

ExternalSymbols[Mangle(“printd”)] = (uint64_t)&printd;

Then in your addModules method you fall back to searching the map if you can’t find the symbol in the JIT:

auto Resolver = createLambdaResolver(
[&](const std::string &Name) {

/// Try the JIT first…
if (auto Sym = findSymbol(Name))
return RuntimeDyld::SymbolInfo(Sym.getAddress(),
Sym.getFlags());

// Try the external symbols map…
if (auto Addr = ExternalSymbols.lookup(Name))
return RuntimeDyld::SymbolInfo(Addr,
JITSymbolFlags::Exported);

// Symbol not found.
return RuntimeDyld::SymbolInfo(nullptr);
},
(const std::string &S) { return nullptr; }
);

Option (2) is to expose all the symbols from the host process. You can see an example of this in tools/lli/OrcLazyJIT.{h,cpp}. Before you construct your JIT you run:

sys::DynamicLibrary::LoadLibraryPermanently(nullptr);

This will make the host process symbols visible to the DynamicLibrary class. Then you can use the RTDyldMemoryManager::getSymbolAddressInProcess method (which uses DynamicLibrary for lookup) to search the host process in your resolver:

auto Resolver = createLambdaResolver(
[&](const std::string &Name) {

if (auto Sym = findSymbol(Name))
return RuntimeDyld::SymbolInfo(Sym.getAddress(),
Sym.getFlags());

if (auto Addr = RTDyldMemoryManager::getSymbolAddressInProcess(Name))
return RuntimeDyld::SymbolInfo(Addr, JITSymbolFlags::Exported);

return RuntimeDyld::SymbolInfo(nullptr);
},
(const std::string &S) { return nullptr; }
};

Hope this helps!

Cheers,
Lang.