Add Linux backtrace support. (#605)
This commit is contained in:
+130
-129
@@ -23,11 +23,11 @@ convention.plugins.platformInfo = new PlatformInfo()
|
||||
|
||||
allprojects {
|
||||
if (path != ":dependencies") {
|
||||
evaluationDependsOn(":dependencies")
|
||||
evaluationDependsOn(":dependencies")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
loadCommandLineProperties()
|
||||
@@ -41,22 +41,22 @@ void setupClang(Project project) {
|
||||
project.convention.plugins.execClang = new org.jetbrains.kotlin.ExecClang(project)
|
||||
|
||||
project.plugins.withType(NativeComponentPlugin) {
|
||||
project.model {
|
||||
toolChains {
|
||||
clang(Clang) {
|
||||
clangPath.each {
|
||||
path it
|
||||
}
|
||||
project.model {
|
||||
toolChains {
|
||||
clang(Clang) {
|
||||
clangPath.each {
|
||||
path it
|
||||
}
|
||||
|
||||
eachPlatform { // TODO: will not work when cross-compiling
|
||||
[cCompiler, cppCompiler, linker].each {
|
||||
it.withArguments { it.addAll(hostClangArgs) }
|
||||
}
|
||||
eachPlatform { // TODO: will not work when cross-compiling
|
||||
[cCompiler, cppCompiler, linker].each {
|
||||
it.withArguments { it.addAll(hostClangArgs) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,54 +67,56 @@ void setupCompilationFlags() {
|
||||
final String binDir
|
||||
|
||||
if (isLinux()) {
|
||||
ext.host = "linux"
|
||||
ext.targetArgs <<
|
||||
[(ext.host):
|
||||
["--sysroot=${gccToolchainDir}/${gnuTriplet}/sysroot"],
|
||||
"raspberrypi":
|
||||
["-target", "armv6-unknown-linux-gnueabihf",
|
||||
"-mfpu=vfp", "-mfloat-abi=hard",
|
||||
"--sysroot=$raspberryPiSysrootDir",
|
||||
// TODO: those two are hacks.
|
||||
"-I$raspberryPiSysrootDir/usr/include/c++/4.8.3",
|
||||
"-I$raspberryPiSysrootDir/usr/include/c++/4.8.3/arm-linux-gnueabihf"]
|
||||
]
|
||||
ext.host = "linux"
|
||||
ext.targetArgs <<
|
||||
[(ext.host):
|
||||
["--sysroot=${gccToolchainDir}/${gnuTriplet}/sysroot",
|
||||
"-DUSE_GCC_UNWIND=1", "-DELFSIZE=64"],
|
||||
"raspberrypi":
|
||||
["-target", "armv6-unknown-linux-gnueabihf",
|
||||
"-mfpu=vfp", "-mfloat-abi=hard",
|
||||
"-DUSE_GCC_UNWIND=1", "-DELFSIZE=32",
|
||||
"--sysroot=$raspberryPiSysrootDir",
|
||||
// TODO: those two are hacks.
|
||||
"-I$raspberryPiSysrootDir/usr/include/c++/4.8.3",
|
||||
"-I$raspberryPiSysrootDir/usr/include/c++/4.8.3/arm-linux-gnueabihf"]
|
||||
]
|
||||
} else {
|
||||
ext.host = "macbook"
|
||||
ext.targetArgs <<
|
||||
[(ext.host):
|
||||
["--sysroot=$macbookSysrootDir", "-mmacosx-version-min=$minMacOsVersion"],
|
||||
"iphone":
|
||||
["-stdlib=libc++", "-arch", "arm64", "-isysroot", "$iphoneSysrootDir",
|
||||
"-miphoneos-version-min=8.0.0"]
|
||||
ext.host = "macbook"
|
||||
ext.targetArgs <<
|
||||
[(ext.host):
|
||||
["--sysroot=$macbookSysrootDir", "-mmacosx-version-min=$minMacOsVersion"],
|
||||
"iphone":
|
||||
["-stdlib=libc++", "-arch", "arm64", "-isysroot", "$iphoneSysrootDir",
|
||||
"-miphoneos-version-min=8.0.0"]
|
||||
// TODO: re-enable after simulator sysroot is available in the dependencies
|
||||
// "iphone_sim":
|
||||
// ["-stdlib=libc++", "-isysroot", "$iphoneSimSysrootDir", "-miphoneos-version-min=8.0.0"],
|
||||
]
|
||||
]
|
||||
}
|
||||
ext.targetArgs << [
|
||||
"android_arm32":
|
||||
["-target", "arm-linux-androideabi",
|
||||
"--sysroot=$android_arm32SysrootDir",
|
||||
// HACKS!
|
||||
/*"-D_LIBCPP_HAS_MUSL_LIBC",*/ "-DOMIT_BACKTRACE", "-D__ANDROID__",
|
||||
"-I$android_arm32SysrootDir/usr/include/c++/4.9.x",
|
||||
"-I$android_arm32SysrootDir/usr/include/c++/4.9.x/arm-linux-androideabi"
|
||||
]
|
||||
["-target", "arm-linux-androideabi",
|
||||
"--sysroot=$android_arm32SysrootDir",
|
||||
"-D__ANDROID__", "-DUSE_GCC_UNWIND=1", "-DELFSIZE=32",
|
||||
// HACKS!
|
||||
"-I$android_arm32SysrootDir/usr/include/c++/4.9.x",
|
||||
"-I$android_arm32SysrootDir/usr/include/c++/4.9.x/arm-linux-androideabi"
|
||||
]
|
||||
]
|
||||
ext.targetList = ext.targetArgs.keySet() as List
|
||||
|
||||
if (isLinux()) {
|
||||
binDir = "$gccToolchainDir/$gnuTriplet/bin"
|
||||
ext.clangArgs.addAll([
|
||||
"--gcc-toolchain=$gccToolchainDir",
|
||||
"-L$llvmDir/lib"
|
||||
])
|
||||
binDir = "$gccToolchainDir/$gnuTriplet/bin"
|
||||
ext.clangArgs.addAll([
|
||||
"--gcc-toolchain=$gccToolchainDir",
|
||||
"-L$llvmDir/lib"
|
||||
])
|
||||
} else {
|
||||
binDir = "$macbookSysrootDir/usr/bin"
|
||||
binDir = "$macbookSysrootDir/usr/bin"
|
||||
}
|
||||
ext.clangArgs << "-B$binDir"
|
||||
ext.hostClangArgs = ext.clangArgs.clone()
|
||||
ext.hostClangArgs = ext.clangArgs.clone()
|
||||
ext.hostClangArgs.addAll(ext.targetArgs[host])
|
||||
|
||||
ext.clangPath << binDir
|
||||
@@ -123,22 +125,22 @@ void setupCompilationFlags() {
|
||||
|
||||
void loadLocalProperties() {
|
||||
if (new File("$project.rootDir/local.properties").exists()) {
|
||||
Properties props = new Properties()
|
||||
props.load(new FileInputStream("$project.rootDir/local.properties"))
|
||||
props.each {prop -> project.ext.set(prop.key, prop.value)}
|
||||
Properties props = new Properties()
|
||||
props.load(new FileInputStream("$project.rootDir/local.properties"))
|
||||
props.each {prop -> project.ext.set(prop.key, prop.value)}
|
||||
}
|
||||
}
|
||||
|
||||
void loadCommandLineProperties() {
|
||||
if (project.hasProperty("konanc_flags")) {
|
||||
throw new Error("Specify either -Ptest_flags or -Pbuild_flags.")
|
||||
throw new Error("Specify either -Ptest_flags or -Pbuild_flags.")
|
||||
}
|
||||
ext.globalBuildArgs = project.hasProperty("build_flags")
|
||||
? ext.build_flags.split(' ') : []
|
||||
? ext.build_flags.split(' ') : []
|
||||
ext.globalTestArgs = project.hasProperty("test_flags")
|
||||
? ext.test_flags.split(' ') : []
|
||||
? ext.test_flags.split(' ') : []
|
||||
ext.testTarget = project.hasProperty("test_target")
|
||||
? ext.test_target : null
|
||||
? ext.test_target : null
|
||||
}
|
||||
|
||||
class PlatformInfo {
|
||||
@@ -146,33 +148,33 @@ class PlatformInfo {
|
||||
private final String osArch = System.properties['os.arch']
|
||||
|
||||
boolean isMac() {
|
||||
return osName == 'Mac OS X'
|
||||
return osName == 'Mac OS X'
|
||||
}
|
||||
|
||||
boolean isLinux() {
|
||||
return osName == 'Linux'
|
||||
return osName == 'Linux'
|
||||
}
|
||||
|
||||
boolean isAmd64() {
|
||||
return osArch in ['x86_64', 'amd64']
|
||||
return osArch in ['x86_64', 'amd64']
|
||||
}
|
||||
|
||||
String getGnuTriplet() {
|
||||
if (isLinux() && isAmd64()) {
|
||||
return "x86_64-unknown-linux-gnu"
|
||||
} else {
|
||||
throw unsupportedPlatformException()
|
||||
}
|
||||
if (isLinux() && isAmd64()) {
|
||||
return "x86_64-unknown-linux-gnu"
|
||||
} else {
|
||||
throw unsupportedPlatformException()
|
||||
}
|
||||
}
|
||||
|
||||
String simpleOsName() {
|
||||
if (isMac()) return 'macos'
|
||||
if (isLinux()) return 'linux'
|
||||
throw unsupportedPlatformException()
|
||||
if (isMac()) return 'macos'
|
||||
if (isLinux()) return 'linux'
|
||||
throw unsupportedPlatformException()
|
||||
}
|
||||
|
||||
Throwable unsupportedPlatformException() {
|
||||
return new Error("unsupported platform: $osName/$osArch")
|
||||
return new Error("unsupported platform: $osName/$osArch")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,53 +190,53 @@ task distCompiler(type: Copy) {
|
||||
destinationDir file('dist')
|
||||
|
||||
from(project(':backend.native').file('build/libs')) {
|
||||
into('konan/lib')
|
||||
into('konan/lib')
|
||||
}
|
||||
|
||||
from(project('Interop').file('Runtime/build/libs')) {
|
||||
into('konan/lib')
|
||||
into('konan/lib')
|
||||
}
|
||||
|
||||
from(project('Interop').file('Indexer/build/libs')) {
|
||||
into('konan/lib')
|
||||
into('konan/lib')
|
||||
}
|
||||
|
||||
from(project('Interop').file('StubGenerator/build/libs')) {
|
||||
into('konan/lib')
|
||||
into('konan/lib')
|
||||
}
|
||||
|
||||
from(project(':backend.native').file('build/external_jars')) {
|
||||
into('konan/lib')
|
||||
into('konan/lib')
|
||||
}
|
||||
|
||||
from(project(':backend.native').file('build/nativelibs')) {
|
||||
into('konan/nativelib')
|
||||
into('konan/nativelib')
|
||||
}
|
||||
|
||||
from(project(':Interop').file('Indexer/build/nativelibs')) {
|
||||
into('konan/nativelib')
|
||||
into('konan/nativelib')
|
||||
}
|
||||
|
||||
from(project(':Interop').file('Runtime/build/nativelibs')) {
|
||||
into('konan/nativelib')
|
||||
into('konan/nativelib')
|
||||
}
|
||||
|
||||
from(project(':llvmDebugInfoC').file('build/libs/debugInfo/shared')) {
|
||||
into('konan/nativelib')
|
||||
into('konan/nativelib')
|
||||
}
|
||||
|
||||
|
||||
from(file('cmd')) {
|
||||
fileMode(0755)
|
||||
into('bin')
|
||||
fileMode(0755)
|
||||
into('bin')
|
||||
}
|
||||
from(project(':backend.native').file('konan.properties')) {
|
||||
into('konan')
|
||||
into('konan')
|
||||
}
|
||||
|
||||
// TODO: check if we use native libraries copied to dist (see above) or not.
|
||||
from(project(':tools:helpers').file('build/libs')) {
|
||||
into('konan/lib')
|
||||
into('konan/lib')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,8 +254,8 @@ task commonDistRuntime(type: Copy) {
|
||||
|
||||
// Target independant common part.
|
||||
from(project(':runtime').file("build/${host}Stdlib")) {
|
||||
include('**')
|
||||
into('klib/stdlib')
|
||||
include('**')
|
||||
into('klib/stdlib')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,25 +266,25 @@ task crossDistRuntime(type: Copy) {
|
||||
|
||||
targetList.each { target ->
|
||||
task("${target}CrossDistRuntime", type: Copy) {
|
||||
dependsOn ":runtime:${target}Runtime"
|
||||
dependsOn ":backend.native:${target}Stdlib"
|
||||
dependsOn ":backend.native:${target}Start"
|
||||
dependsOn ":runtime:${target}Runtime"
|
||||
dependsOn ":backend.native:${target}Stdlib"
|
||||
dependsOn ":backend.native:${target}Start"
|
||||
|
||||
destinationDir file('dist')
|
||||
destinationDir file('dist')
|
||||
|
||||
from(project(':runtime').file("build/$target")) {
|
||||
include("*.bc")
|
||||
into("klib/stdlib/$target/native")
|
||||
}
|
||||
from(project(':runtime').file("build/${target}Stdlib")) {
|
||||
include('**')
|
||||
into('klib/stdlib')
|
||||
}
|
||||
from(project(':runtime').file("build/${target}Start/$target/kotlin")) {
|
||||
include("program.kt.bc")
|
||||
rename('program.kt.bc', 'start.kt.bc')
|
||||
into("klib/stdlib/$target/native")
|
||||
}
|
||||
from(project(':runtime').file("build/$target")) {
|
||||
include("*.bc")
|
||||
into("klib/stdlib/$target/native")
|
||||
}
|
||||
from(project(':runtime').file("build/${target}Stdlib")) {
|
||||
include('**')
|
||||
into('klib/stdlib')
|
||||
}
|
||||
from(project(':runtime').file("build/${target}Start/$target/kotlin")) {
|
||||
include("program.kt.bc")
|
||||
rename('program.kt.bc', 'start.kt.bc')
|
||||
into("klib/stdlib/$target/native")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,33 +300,33 @@ task bundle(type: Tar) {
|
||||
dependsOn('crossDist')
|
||||
baseName = "kotlin-native-${simpleOsName()}-${project.konanVersion}"
|
||||
from("$project.rootDir/dist") {
|
||||
include '**'
|
||||
exclude 'dependencies'
|
||||
into baseName
|
||||
include '**'
|
||||
exclude 'dependencies'
|
||||
into baseName
|
||||
}
|
||||
from(project.rootDir) {
|
||||
include 'DISTRO_README.md'
|
||||
rename {
|
||||
return "README.md"
|
||||
}
|
||||
into baseName
|
||||
include 'DISTRO_README.md'
|
||||
rename {
|
||||
return "README.md"
|
||||
}
|
||||
into baseName
|
||||
}
|
||||
from(project.rootDir) {
|
||||
include 'samples/**'
|
||||
include 'INTEROP.md'
|
||||
include 'RELEASE_NOTES.md'
|
||||
include 'GRADLE_PLUGIN.md'
|
||||
exclude '**/gradle.properties'
|
||||
exclude '**/build'
|
||||
exclude '**/.gradle'
|
||||
rename { String fileName ->
|
||||
if (fileName == "gradle.properties.for_bundle") {
|
||||
return "gradle.properties"
|
||||
} else {
|
||||
return fileName
|
||||
}
|
||||
}
|
||||
into baseName
|
||||
include 'samples/**'
|
||||
include 'INTEROP.md'
|
||||
include 'RELEASE_NOTES.md'
|
||||
include 'GRADLE_PLUGIN.md'
|
||||
exclude '**/gradle.properties'
|
||||
exclude '**/build'
|
||||
exclude '**/.gradle'
|
||||
rename { String fileName ->
|
||||
if (fileName == "gradle.properties.for_bundle") {
|
||||
return "gradle.properties"
|
||||
} else {
|
||||
return fileName
|
||||
}
|
||||
}
|
||||
into baseName
|
||||
}
|
||||
destinationDir = file('.')
|
||||
extension = 'tar.gz'
|
||||
@@ -339,10 +341,9 @@ task demo(type: Exec) {
|
||||
|
||||
task clean {
|
||||
doLast {
|
||||
file('dist').traverse(type: FileType.ANY, excludeNameFilter: "dependencies", maxDepth: 0) {
|
||||
delete it
|
||||
}
|
||||
delete bundle.outputs.files
|
||||
file('dist').traverse(type: FileType.ANY, excludeNameFilter: "dependencies", maxDepth: 0) {
|
||||
delete it
|
||||
}
|
||||
delete bundle.outputs.files
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,19 +13,29 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef OMIT_BACKTRACE
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef OMIT_BACKTRACE
|
||||
#if USE_GCC_UNWIND
|
||||
// GCC unwinder for backtrace.
|
||||
#include <unwind.h>
|
||||
#else
|
||||
// Glibc backtrace function.
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
#endif // OMIT_BACKTRACE
|
||||
|
||||
#include "Assert.h"
|
||||
#include "Exceptions.h"
|
||||
#include "ExecFormat.h"
|
||||
#include "Memory.h"
|
||||
#include "Natives.h"
|
||||
#include "KString.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class KotlinException {
|
||||
public:
|
||||
|
||||
@@ -53,6 +63,50 @@ class AutoFree {
|
||||
}
|
||||
};
|
||||
|
||||
#if USE_GCC_UNWIND
|
||||
struct Backtrace {
|
||||
Backtrace(int count, int skip) : index(0), skipCount(skip), array(nullptr) {
|
||||
auto result = AllocArrayInstance(
|
||||
theArrayTypeInfo, count - skipCount, &array);
|
||||
RuntimeAssert(result != nullptr, "Cannot create backtrace array");
|
||||
}
|
||||
|
||||
void setNextElement(const char* element) {
|
||||
CreateStringFromCString(
|
||||
element, ArrayAddressOfElementAt(array->array(), index++));
|
||||
}
|
||||
|
||||
int index;
|
||||
int skipCount;
|
||||
ObjHeader* array;
|
||||
};
|
||||
|
||||
_Unwind_Reason_Code depthCountCallback(
|
||||
struct _Unwind_Context * context, void* arg) {
|
||||
int* result = reinterpret_cast<int*>(arg);
|
||||
(*result)++;
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
|
||||
_Unwind_Reason_Code unwindCallback(
|
||||
struct _Unwind_Context* context, void* arg) {
|
||||
Backtrace* backtrace = reinterpret_cast<Backtrace*>(arg);
|
||||
if (backtrace->skipCount > 0) {
|
||||
backtrace->skipCount--;
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
unsigned long ip = _Unwind_GetIP(context);
|
||||
const char* symbol = AddressToSymbol(ip);
|
||||
char line[512];
|
||||
snprintf(line, sizeof(line) - 1, "%s (0x%lx)",
|
||||
symbol != nullptr ? symbol : "", ip);
|
||||
backtrace->setNextElement(line);
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -65,6 +119,14 @@ OBJ_GETTER0(GetCurrentStackTrace) {
|
||||
ArrayHeader* array = result->array();
|
||||
CreateStringFromCString("<UNIMPLEMENTED>", ArrayAddressOfElementAt(array, 0));
|
||||
return result;
|
||||
#else
|
||||
#if USE_GCC_UNWIND
|
||||
int depth = 0;
|
||||
_Unwind_Backtrace(depthCountCallback, &depth);
|
||||
// Skips first 3 elements as irrelevant.
|
||||
Backtrace result(depth, 3);
|
||||
_Unwind_Backtrace(unwindCallback, &result);
|
||||
RETURN_OBJ(result.array);
|
||||
#else
|
||||
const int maxSize = 32;
|
||||
void* buffer[maxSize];
|
||||
@@ -82,11 +144,12 @@ OBJ_GETTER0(GetCurrentStackTrace) {
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
#endif // !OMIT_BACKTRACE
|
||||
}
|
||||
|
||||
void ThrowException(KRef exception) {
|
||||
RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo),
|
||||
"Throwing something non-throwable");
|
||||
"Throwing something non-throwable");
|
||||
throw KotlinException(exception);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#if USE_GCC_UNWIND
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <elf.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "Assert.h"
|
||||
|
||||
namespace {
|
||||
|
||||
#if !defined(ELFSIZE)
|
||||
#error "Define ELFSIZE to 32 or 64"
|
||||
#endif
|
||||
|
||||
#define CONCAT(x,y) __CONCAT(x,y)
|
||||
#define ELFNAME(x) CONCAT(elf,CONCAT(ELFSIZE,CONCAT(_,x)))
|
||||
#define ELFNAME2(x,y) CONCAT(x,CONCAT(_elf,CONCAT(ELFSIZE,CONCAT(_,y))))
|
||||
#define ELFNAMEEND(x) CONCAT(x,CONCAT(_elf,ELFSIZE))
|
||||
#define ELFDEFNNAME(x) CONCAT(ELF,CONCAT(ELFSIZE,CONCAT(_,x)))
|
||||
|
||||
#if ELFSIZE == 32
|
||||
#define Elf_Ehdr Elf32_Ehdr
|
||||
#define Elf_Phdr Elf32_Phdr
|
||||
#define Elf_Shdr Elf32_Shdr
|
||||
#define Elf_Sym Elf32_Sym
|
||||
#elif ELFSIZE == 64
|
||||
#define Elf_Ehdr Elf64_Ehdr
|
||||
#define Elf_Phdr Elf64_Phdr
|
||||
#define Elf_Shdr Elf64_Shdr
|
||||
#define Elf_Sym Elf64_Sym
|
||||
#else
|
||||
#error "Impossible ELFSIZE"
|
||||
#endif
|
||||
|
||||
struct SymRecord {
|
||||
Elf_Sym* symtabBegin;
|
||||
Elf_Sym* symtabEnd;
|
||||
char* strtab;
|
||||
};
|
||||
|
||||
std::vector<SymRecord>* symbols = nullptr;
|
||||
char* mapAddress = nullptr;
|
||||
|
||||
// Unfortunately, symbol tables are stored in ELF sections not mapped
|
||||
// during regular execution, so we have to map binary ourselves.
|
||||
Elf_Ehdr* findElfHeader() {
|
||||
int fd = open("/proc/self/exe", O_RDONLY);
|
||||
if (fd < 0) return nullptr;
|
||||
struct stat fd_stat;
|
||||
if (fstat(fd, &fd_stat) < 0) return nullptr;
|
||||
void* result = mmap(nullptr, fd_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (result == MAP_FAILED) return nullptr;
|
||||
return (Elf_Ehdr*)result;
|
||||
}
|
||||
|
||||
void initSymbols() {
|
||||
RuntimeAssert(symbols == nullptr, "Init twice");
|
||||
symbols = new std::vector<SymRecord>();
|
||||
Elf_Ehdr* ehdr = findElfHeader();
|
||||
if (ehdr == nullptr) return;
|
||||
RuntimeAssert(strncmp((const char*)ehdr->e_ident, ELFMAG, SELFMAG) == 0, "Must be an ELF");
|
||||
char* mapAddress = (char*)ehdr;
|
||||
Elf_Shdr* shdr = (Elf_Shdr*)(mapAddress + ehdr->e_shoff);
|
||||
for (int i = 0; i < ehdr->e_shnum; i++) {
|
||||
if (shdr[i].sh_type == SHT_SYMTAB) { // Static symbol table.
|
||||
SymRecord record;
|
||||
record.symtabBegin = (Elf_Sym*)(mapAddress + shdr[i].sh_offset);
|
||||
record.symtabEnd = (Elf_Sym*)((char*)record.symtabBegin + shdr[i].sh_size);
|
||||
record.strtab = (char *)(mapAddress + shdr[shdr[i].sh_link].sh_offset);
|
||||
symbols->push_back(record);
|
||||
}
|
||||
if (shdr[i].sh_type == SHT_DYNSYM) { // Dynamic symbol table.
|
||||
SymRecord record;
|
||||
record.symtabBegin = (Elf_Sym*)(mapAddress + shdr[i].sh_offset);
|
||||
record.symtabEnd = (Elf_Sym*)((char*)record.symtabBegin + shdr[i].sh_size);
|
||||
record.strtab = (char*)(mapAddress + shdr[shdr[i].sh_link].sh_offset);
|
||||
symbols->push_back(record);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" const char* AddressToSymbol(unsigned long address) {
|
||||
if (address == 0) return nullptr;
|
||||
|
||||
// First, look up in dynamically loaded symbols.
|
||||
Dl_info info;
|
||||
if (dladdr((const void*)address, &info) != 0 && info.dli_sname != nullptr) {
|
||||
return info.dli_sname;
|
||||
}
|
||||
|
||||
// Otherwise, consult symbol table of the file.
|
||||
if (symbols == nullptr) {
|
||||
initSymbols();
|
||||
}
|
||||
|
||||
for (auto record : *symbols) {
|
||||
auto begin = record.symtabBegin;
|
||||
auto end = record.symtabEnd;
|
||||
while (begin < end) {
|
||||
if (address >= (unsigned long)mapAddress + begin->st_value &&
|
||||
address < (unsigned long)mapAddress + begin->st_value + begin->st_size) {
|
||||
return &record.strtab[begin->st_name];
|
||||
}
|
||||
begin++;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
extern "C" const char* AddressToSymbol(unsigned long address) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif // USE_GCC_UNWIND
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_EXECFORMAT_H
|
||||
#define RUNTIME_EXECFORMAT_H
|
||||
|
||||
extern "C" {
|
||||
|
||||
const char* AddressToSymbol(unsigned long address);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // RUNTIME_EXECFORMAT_H
|
||||
Reference in New Issue
Block a user