Revert "Import Dexter to debuginfo-tests"

This reverts commit f78c236efd.

Green dragon breakage was observed; I'll take a look at why.
This commit is contained in:
Jeremy Morse 2019-10-31 16:22:10 +00:00
parent f2cb9c0eab
commit cb935f3456
166 changed files with 313 additions and 9920 deletions

View File

@ -13,47 +13,15 @@ set(DEBUGINFO_TEST_DEPS
not
)
# Wipe, uh, previous results
unset(PYTHONINTERP_FOUND CACHE)
unset(PYTHON_EXECUTABLE CACHE)
unset(PYTHON_LIBRARY CACHE)
unset(PYTHON_DLL CACHE)
unset(PYTHON_INCLUDE_DIR CACHE)
unset(PYTHON_VERSION_STRING CACHE)
unset(PYTHON_VERSION_MAJOR CACHE)
unset(PYTHON_VERSION_MINOR CACHE)
unset(PYTHON_VERSION_PATCH CACHE)
unset(PYTHONLIBS_VERSION_STRING CACHE)
configure_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py
MAIN_CONFIG
${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py
)
# Try to find python3. If it doesn't exist, dexter tests can't run.
find_package(PythonInterp "3")
if (NOT DEFINED PYTHON_EXECUTABLE)
message(FATAL_ERROR "Cannot run debuginfo-tests without python")
elseif(PYTHON_VERSION_MAJOR LESS 3)
message(FATAL_ERROR "Cannot run debuginfo-tests without python 3")
else()
configure_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py
MAIN_CONFIG
${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py
)
add_lit_testsuite(check-debuginfo "Running debug info integration tests"
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${DEBUGINFO_TEST_DEPS}
)
set_target_properties(check-debuginfo PROPERTIES FOLDER "Debug info tests")
endif()
# Prevent the rest of llvm observing our secret python3-ness
unset(PYTHONINTERP_FOUND CACHE)
unset(PYTHON_EXECUTABLE CACHE)
unset(PYTHON_LIBRARY CACHE)
unset(PYTHON_DLL CACHE)
unset(PYTHON_INCLUDE_DIR CACHE)
unset(PYTHON_VERSION_STRING CACHE)
unset(PYTHON_VERSION_MAJOR CACHE)
unset(PYTHON_VERSION_MINOR CACHE)
unset(PYTHON_VERSION_PATCH CACHE)
unset(PYTHONLIBS_VERSION_STRING CACHE)
add_lit_testsuite(check-debuginfo "Running debug info integration tests"
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${DEBUGINFO_TEST_DEPS}
)
set_target_properties(check-debuginfo PROPERTIES FOLDER "Debug info tests")

View File

@ -1,11 +1,9 @@
-*- rst -*-
This is a collection of tests to check debugging information generated by
compiler. This test suite can be checked out inside clang/test folder. This
will enable 'make test' for clang to pick up these tests.
Some tests (in the 'llgdb-tests' directory) are written with debugger
commands and checks for the intended debugger output in the source file,
using DEBUGGER: and CHECK: as prefixes respectively.
will enable 'make test' for clang to pick up these tests. Typically, test
cases included here includes debugger commands and intended debugger output
as comments in source file using DEBUGGER: and CHECK: as prefixes respectively.
For example::
@ -19,25 +17,3 @@ For example::
is a testcase where the debugger is asked to break at function 'f1' and
print value of argument 'i'. The expected value of 'i' is 42 in this case.
Other tests are written for use with the 'Dexter' tool (in the 'dexter-tests'
and 'dexter' directories respectively). These use a domain specific language
in comments to describe the intended debugger experience in a more abstract
way than debugger commands. This allows for testing integration across
multiple debuggers from one input language.
For example::
void __attribute__((noinline, optnone)) bar(int *test) {}
int main() {
int test;
test = 23;
bar(&test); // DexLabel('before_bar')
return test; // DexLabel('after_bar')
}
// DexExpectWatchValue('test', '23', on_line='before_bar')
// DexExpectWatchValue('test', '23', on_line='after_bar')
Labels two lines with the names 'before_bar' and 'after_bar', and records that
the 'test' variable is expected to have the value 23 on both of them.

View File

@ -0,0 +1,32 @@
// RUN: %clangxx %target_itanium_abi_host_triple -O0 -g %s -c -o %t.o
// RUN: %clangxx %target_itanium_abi_host_triple %t.o -o %t.out
// RUN: %test_debuginfo %s %t.out
// Radar 8945514
// DEBUGGER: break 22
// DEBUGGER: r
// DEBUGGER: p v
// CHECK: ${{[0-9]+}} =
// CHECK: Data ={{.*}} 0x0{{(0*)}}
// CHECK: Kind = 2142
class SVal {
public:
~SVal() {}
const void* Data;
unsigned Kind;
};
void bar(SVal &v) {}
class A {
public:
void foo(SVal v) { bar(v); }
};
int main() {
SVal v;
v.Data = 0;
v.Kind = 2142;
A a;
a.foo(v);
return 0;
}

View File

@ -1,7 +1,7 @@
// RUN: %clang -fblocks %target_itanium_abi_host_triple -arch x86_64 %s -o %t.out -g -fsanitize=address
// RUN: %test_debuginfo %s %t.out
// FIXME: Remove system-darwin when we build BlocksRuntime everywhere.
// REQUIRES: !asan, system-darwin
// REQUIRES: not_asan, system-darwin
// Zorg configures the ASAN stage2 bots to not build the asan
// compiler-rt. Only run this test on non-asanified configurations.
void b();

View File

@ -1,6 +1,6 @@
// RUN: %clangxx -arch x86_64 %target_itanium_abi_host_triple -O1 -g %s -o %t.out -fsanitize=address
// RUN: %test_debuginfo %s %t.out
// REQUIRES: !asan
// REQUIRES: not_asan
// Zorg configures the ASAN stage2 bots to not build the asan
// compiler-rt. Only run this test on non-asanified configurations.
// UNSUPPORTED: apple-lldb-pre-1000

View File

@ -1,6 +1,6 @@
// RUN: %clang -fblocks %target_itanium_abi_host_triple -arch x86_64 %s -o %t.out -g -fsanitize=address
// RUN: %test_debuginfo %s %t.out
// REQUIRES: !asan
// REQUIRES: not_asan
// Zorg configures the ASAN stage2 bots to not build the asan
// compiler-rt. Only run this test on non-asanified configurations.
//

25
debuginfo-tests/ctor.cpp Normal file
View File

@ -0,0 +1,25 @@
// RUN: %clangxx %target_itanium_abi_host_triple -O0 -g %s -c -o %t.o
// RUN: %clangxx %target_itanium_abi_host_triple %t.o -o %t.out
// RUN: %test_debuginfo %s %t.out
// DEBUGGER: break 14
// DEBUGGER: r
// DEBUGGER: p *this
// CHECK-NEXT-NOT: Cannot access memory at address
class A {
public:
A() : zero(0), data(42)
{
}
private:
int zero;
int data;
};
int main() {
A a;
return 0;
}

View File

@ -1,9 +1,12 @@
// REQUIRES: system-linux, lldb
//
// This test case checks debug info during register moves for an argument.
// RUN: %dexter --fail-lt 1.0 -w \
// RUN: --builder clang-c --debugger 'lldb' \
// RUN: --cflags "-m64 -mllvm -fast-isel=false -g" -- %s
// RUN: %clang %target_itanium_abi_host_triple -m64 -mllvm -fast-isel=false %s -c -o %t.o -g
// RUN: %clang %target_itanium_abi_host_triple -m64 %t.o -o %t.out
// RUN: %test_debuginfo %s %t.out
//
// DEBUGGER: break 26
// DEBUGGER: r
// DEBUGGER: print mutex
// CHECK: ={{.* 0x[0-9A-Fa-f]+}}
//
// Radar 8412415
@ -20,7 +23,7 @@ struct _mtx
int foobar(struct _mtx *mutex) {
int r = 1;
int l = 0; // DexLabel('l_assign')
int l = 0;
int j = 0;
do {
if (mutex->waiters) {
@ -41,18 +44,3 @@ int main() {
m.waiters = 0;
return foobar(&m);
}
/*
DexExpectProgramState({
'frames': [
{
'location': { 'lineno': 'l_assign' },
'watches': {
'*mutex': { 'is_irretrievable': False }
}
}
]
})
*/

View File

@ -1,43 +0,0 @@
// REQUIRES: system-linux, lldb
//
// RUN: %dexter --fail-lt 1.0 -w \
// RUN: --builder 'clang' --debugger 'lldb' --cflags "-O0 -g" \
// RUN: --ldflags="-lstdc++" -- %s
// Radar 8945514
class SVal {
public:
~SVal() {}
const void* Data;
unsigned Kind;
};
void bar(SVal &v) {}
class A {
public:
void foo(SVal v) { bar(v); } // DexLabel('foo')
};
int main() {
SVal v;
v.Data = 0;
v.Kind = 2142;
A a;
a.foo(v);
return 0;
}
/*
DexExpectProgramState({
'frames': [
{
'location': { 'lineno': 'foo' },
'watches': {
'v.Data == 0': 'true',
'v.Kind': '2142'
}
}
]
})
*/

View File

@ -1,47 +0,0 @@
// REQUIRES: !asan, system-linux, lldb
// Zorg configures the ASAN stage2 bots to not build the asan
// compiler-rt. Only run this test on non-asanified configurations.
// UNSUPPORTED: apple-lldb-pre-1000
// XFAIL: lldb
// lldb-8, even outside of dexter, will sometimes trigger an asan fault in
// the debugged process and generally freak out.
// RUN: %dexter --fail-lt 1.0 -w \
// RUN: --builder 'clang' --debugger 'lldb' \
// RUN: --cflags "-O1 -glldb -fsanitize=address -arch x86_64" \
// RUN: --ldflags="-fsanitize=address" -- %s
#include <deque>
struct A {
int a;
A(int a) : a(a) {}
A() : a(0) {}
};
using deq_t = std::deque<A>;
template class std::deque<A>;
static void __attribute__((noinline, optnone)) escape(deq_t &deq) {
static volatile deq_t *sink;
sink = &deq;
}
int main() {
deq_t deq;
deq.push_back(1234);
deq.push_back(56789);
escape(deq); // DexLabel('first')
while (!deq.empty()) {
auto record = deq.front();
deq.pop_front();
escape(deq); // DexLabel('second')
}
}
// DexExpectWatchValue('deq[0].a', '1234', on_line='first')
// DexExpectWatchValue('deq[1].a', '56789', on_line='first')
// DexExpectWatchValue('deq[0].a', '56789', '0', on_line='second')

View File

@ -1,28 +0,0 @@
// REQUIRES: !asan, system-linux, lldb
// Zorg configures the ASAN stage2 bots to not build the asan
// compiler-rt. Only run this test on non-asanified configurations.
//
// RUN: %dexter --fail-lt 1.0 -w \
// RUN: --builder 'clang-c' --debugger 'lldb' \
// RUN: --cflags "--driver-mode=gcc -O0 -glldb -fblocks -arch x86_64 \
// RUN: -fsanitize=address" --ldflags="-fsanitize=address" -- %s
struct S {
int a[8];
};
int f(struct S s, unsigned i) {
return s.a[i]; // DexLabel('asan')
}
int main(int argc, const char **argv) {
struct S s = {{0, 1, 2, 3, 4, 5, 6, 7}};
if (f(s, 4) == 4)
return f(s, 0);
return 0;
}
// DexExpectWatchValue('s.a[0]', '0', on_line='asan')
// DexExpectWatchValue('s.a[1]', '1', on_line='asan')
// DexExpectWatchValue('s.a[7]', '7', on_line='asan')

View File

@ -1,35 +0,0 @@
// REQUIRES: system-linux, lldb
//
// RUN: %dexter --fail-lt 1.0 -w \
// RUN: --builder 'clang' --debugger 'lldb' --cflags "-O0 -glldb" -- %s
class A {
public:
A() : zero(0), data(42) { // DexLabel('ctor_start')
}
private:
int zero;
int data;
};
int main() {
A a;
return 0;
}
/*
DexExpectProgramState({
'frames': [
{
'location': {
'lineno': 'ctor_start'
},
'watches': {
'*this': {'is_irretrievable': False}
}
}
]
})
*/

View File

@ -1,30 +0,0 @@
// REQUIRES: system-windows
//
// RUN: %dexter --fail-lt 1.0 -w --builder 'clang-cl_vs2015' \
// RUN: --debugger 'dbgeng' --cflags '/Z7 /Zi' --ldflags '/Z7 /Zi' -- %s
// Check that global constants have debug info.
const float TestPi = 3.14;
struct S {
static const char TestCharA = 'a';
};
enum TestEnum : int {
ENUM_POS = 2147000000,
ENUM_NEG = -2147000000,
};
void useConst(int) {}
int main() {
useConst(TestPi);
useConst(S::TestCharA);
useConst(ENUM_NEG); // DexLabel('stop')
return 0;
}
// DexExpectWatchValue('TestPi', 3.140000104904175, on_line='stop')
// DexExpectWatchValue('S::TestCharA', 97, on_line='stop')
// DexExpectWatchValue('ENUM_NEG', -2147000000, on_line='stop')
/* DexExpectProgramState({'frames': [{
'location': {'lineno' : 'stop'},
'watches': {'ENUM_POS' : {'is_irretrievable': True}}
}]}) */

View File

@ -1,13 +0,0 @@
// REQUIRES: system-windows
//
// RUN: %dexter --fail-lt 1.0 -w --builder 'clang-cl_vs2015' \
// RUN: --debugger 'dbgeng' --cflags '/Z7 /Zi' --ldflags '/Z7 /Zi' -- %s
#include <stdio.h>
int main() {
printf("hello world\n");
int x = 42;
__debugbreak(); // DexLabel('stop')
}
// DexExpectWatchValue('x', 42, on_line='stop')

View File

@ -1,55 +0,0 @@
// Purpose:
// This ensures that DW_OP_deref is inserted when necessary, such as when
// NRVO of a string object occurs in C++.
//
// REQUIRES: !asan, system-linux, lldb
// Zorg configures the ASAN stage2 bots to not build the asan
// compiler-rt. Only run this test on non-asanified configurations.
//
// RUN: %dexter --fail-lt 1.0 -w \
// RUN: --builder 'clang' --debugger 'lldb' \
// RUN: --cflags "-O0 -glldb -fno-exceptions" -- %s
//
// RUN: %dexter --fail-lt 1.0 -w \
// RUN: --builder 'clang' --debugger 'lldb' \
// RUN: --cflags "-O1 -glldb -fno-exceptions" -- %s
//
// PR34513
volatile int sideeffect = 0;
void __attribute__((noinline)) stop() { sideeffect++; }
struct string {
string() {}
string(int i) : i(i) {}
~string() {}
int i = 0;
};
string get_string() {
string unused;
string output = 3;
stop(); // DexLabel('string-nrvo')
return output;
}
void some_function(int) {}
struct string2 {
string2() = default;
string2(string2 &&other) { i = other.i; }
int i;
};
string2 get_string2() {
string2 output;
output.i = 5;
some_function(output.i);
// Test that the debugger can get the value of output after another
// function is called.
stop(); // DexLabel('string2-nrvo')
return output;
}
int main() {
get_string();
get_string2();
}
// DexExpectWatchValue('output.i', 3, on_line='string-nrvo')
// DexExpectWatchValue('output.i', 5, on_line='string2-nrvo')

View File

@ -1,39 +0,0 @@
// REQUIRES: system-windows
//
// RUN: %dexter --fail-lt 1.0 -w --builder 'clang-cl_vs2015' \
// RUN: --debugger 'dbgeng' --cflags '/Z7 /Zi' --ldflags '/Z7 /Zi' -- %s
// From https://llvm.org/pr38857, where we had issues with stack realignment.
struct Foo {
int x = 42;
int __declspec(noinline) foo();
void __declspec(noinline) bar(int *a, int *b, double *c);
};
int Foo::foo() {
int a = 1;
int b = 2;
double __declspec(align(32)) force_alignment = 0.42;
bar(&a, &b, &force_alignment); // DexLabel('in_foo')
x += (int)force_alignment;
return x;
}
void Foo::bar(int *a, int *b, double *c) {
*c += *a + *b; // DexLabel('in_bar')
}
int main() {
Foo o;
o.foo();
}
/*
DexExpectProgramState({'frames':[
{'function': 'Foo::bar', 'location' : {'lineno' : 'in_bar'} },
{'function': 'Foo::foo',
'watches' : {
'a' : '1',
'b' : '2',
'force_alignment' : '0.42'
}
}
]})
*/

View File

@ -1,16 +0,0 @@
// REQUIRES: system-linux, lldb
//
// RUN: %dexter --fail-lt 1.0 -w \
// RUN: --builder clang-c --debugger 'lldb' --cflags "-O -glldb" -- %s
void __attribute__((noinline, optnone)) bar(int *test) {}
int main() {
int test;
test = 23;
bar(&test); // DexLabel('before_bar')
return test; // DexLabel('after_bar')
}
// DexExpectWatchValue('test', '23', on_line='before_bar')
// DexExpectWatchValue('test', '23', on_line='after_bar')

View File

@ -1,22 +0,0 @@
// This test case verifies the debug location for variable-length arrays.
// REQUIRES: system-linux, lldb
//
// RUN: %dexter --fail-lt 1.0 -w \
// RUN: --builder clang-c --debugger 'lldb' --cflags "-O0 -glldb" -- %s
void init_vla(int size) {
int i;
int vla[size];
for (i = 0; i < size; i++)
vla[i] = size-i;
vla[0] = size; // DexLabel('end_init')
}
int main(int argc, const char **argv) {
init_vla(23);
return 0;
}
// DexExpectWatchValue('vla[0]', '23', on_line='end_init')
// DexExpectWatchValue('vla[1]', '22', on_line='end_init')

View File

@ -1,3 +0,0 @@
/build/
/results/

View File

@ -1,204 +0,0 @@
# Dexter commands
* [DexExpectProgramState](Commands.md#DexExpectProgramState)
* [DexExpectStepKind](Commands.md#DexExpectStepKind)
* [DexExpectStepOrder](Commands.md#DexExpectStepOrder)
* [DexExpectWatchType](Commands.md#DexExpectWatchType)
* [DexExpectWatchValue](Commands.md#DexExpectWatchValue)
* [DexUnreachable](Commands.md#DexUnreachable)
* [DexWatch](Commands.md#DexWatch)
---
## DexExpectProgramState
DexExpectProgramState(state [,**times])
Args:
state (dict): { 'frames': [
{
# StackFrame #
'function': name (str),
'is_inlined': bool,
'location': {
# SourceLocation #
'lineno': int,
'path': str,
'column': int,
},
'watches': {
expr (str): value (str),
expr (str): {
'value': str,
'type_name': str,
'could_evaluate': bool,
'is_optimized_away': bool,
'is_irretrievable': bool,
}
},
}
]}
Keyword args:
times (int): Minimum number of times this state pattern is expected to
be seen. Defaults to 1. Can be 0.
### Description
Expect to see a given program `state` a certain number of `times`.
For every debugger step the reported state is compared with the expected state.
To consider the states a match:
* The `SourceLocation` must match in both states. Omitted fields in the
`SourceLocation` dictionary are ignored; they always match.
* Each `expr` in `watches` in the expected state can either be a dictionary
with the fields shown above, or a string representing its value. In either
case, the actual value of `expr` in the debugger must match.
* The function name and inline status are not considered.
### Heuristic
[TODO]
---
## DexExpectStepKind
DexExpectStepKind(kind, times)
Args:
kind (str): Expected step kind.
times (int): Expected number of encounters.
### Description
Expect to see a particular step `kind` a number of `times` while stepping
through the program.
`kind` must be one of:
`FUNC`: The first step into a function which is defined in the test
directory.</br>
`FUNC_EXTERNAL`: A step over a function which is not defined in the test
directory.</br>
`FUNC_UNKNOWN`: The first step over a function an unknown definition
location.</br>
`VERTICAL_FORWARD`: A step onto a line after the previous step line in this
frame.</br>
`VERTICAL_BACKWARD`: A step onto a line before the previous step line in
this frame.</br>
`HORIZONTAL_FORWARD`: A step forward on the same line as the previous step in
this frame.</br>
`HORIZONTAL_BACKWARD`: A step backward on the same line as the previous step
in this frame.</br>
`SAME`: A step onto the same line and column as the previous step in this
frame.</br>
### Heuristic
[TODO]
---
## DexExpectStepOrder
DexExpectStepOrder(*order)
Arg list:
order (int): One or more indices.
### Description
Expect the line every `DexExpectStepOrder` is found on to be stepped on in
`order`. Each instance must have a set of unique ascending indices.
### Heuristic
[TODO]
---
## DexExpectWatchType
DexExpectWatchType(expr, *types [,**from_line=1][,**to_line=Max]
[,**on_line][,**require_in_order=True])
Args:
expr (str): expression to evaluate.
Arg list:
types (str): At least one expected type. NOTE: string type.
Keyword args:
from_line (int): Evaluate the expression from this line. Defaults to 1.
to_line (int): Evaluate the expression to this line. Defaults to end of
source.
on_line (int): Only evaluate the expression on this line. If provided,
this overrides from_line and to_line.
require_in_order (bool): If False the values can appear in any order.
### Description
Expect the expression `expr` to evaluate be evaluated and have each evaluation's
type checked against the list of `types`
### Heuristic
[TODO]
---
## DexExpectWatchValue
DexExpectWatchValue(expr, *values [,**from_line=1][,**to_line=Max]
[,**on_line][,**require_in_order=True])
Args:
expr (str): expression to evaluate.
Arg list:
values (str): At least one expected value. NOTE: string type.
Keyword args:
from_line (int): Evaluate the expression from this line. Defaults to 1.
to_line (int): Evaluate the expression to this line. Defaults to end of
source.
on_line (int): Only evaluate the expression on this line. If provided,
this overrides from_line and to_line.
require_in_order (bool): If False the values can appear in any order.
### Description
Expect the expression `expr` to evaluate to the list of `values`
sequentially.
### Heuristic
[TODO]
---
## DexUnreachable
DexUnreachable()
### Description
Expect the source line this is found on will never be stepped on to.
### Heuristic
[TODO]
----
## DexLabel
DexLabel(name)
Args:
name (str): A unique name for this line.
### Description
Name the line this command is found on. Line names can be referenced by other
commands expecting line number arguments.
For example, `DexExpectWatchValues(..., on_line='my_line_name')`.
### Heuristic
This command does not contribute to the heuristic score.
---
## DexWatch
DexWatch(*expressions)
Arg list:
expressions (str): `expression` to evaluate on this line.
### Description
[Deprecated] Evaluate each given `expression` when the debugger steps onto the
line this command is found on.
### Heuristic
[Deprecated]

View File

@ -1,279 +0,0 @@
==============================================================================
The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
==============================================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
---- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.
==============================================================================
Software from third parties included in the LLVM Project:
==============================================================================
The LLVM Project contains third party software which is under different license
terms. All such code will be identified clearly using at least one of two
mechanisms:
1) It will be in a separate directory tree with its own `LICENSE.txt` or
`LICENSE` file at the top containing the specific license and restrictions
which apply to that software, or
2) It will contain specific license and restriction terms at the top of every
file.
==============================================================================
Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy):
==============================================================================
University of Illinois/NCSA
Open Source License
Copyright (c) 2003-2019 University of Illinois at Urbana-Champaign.
All rights reserved.
Developed by:
LLVM Team
University of Illinois at Urbana-Champaign
http://llvm.org
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal with
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimers.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimers in the
documentation and/or other materials provided with the distribution.
* Neither the names of the LLVM Team, University of Illinois at
Urbana-Champaign, nor the names of its contributors may be used to
endorse or promote products derived from this Software without specific
prior written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE.

View File

@ -1,304 +0,0 @@
# DExTer (Debugging Experience Tester)
## Introduction
DExTer is a suite of tools used to evaluate the "User Debugging Experience". DExTer drives an external debugger, running on small test programs, and collects information on the behavior at each debugger step to provide quantitative values that indicate the quality of the debugging experience.
## Supported Debuggers
DExTer currently supports the Visual Studio 2015 and Visual Studio 2017 debuggers via the [DTE interface](https://docs.microsoft.com/en-us/dotnet/api/envdte.dte), and LLDB via its [Python interface](https://lldb.llvm.org/python-reference.html). GDB is not currently supported.
The following command evaluates your environment, listing the available and compatible debuggers:
dexter.py list-debuggers
## Dependencies
[TODO] Add a requirements.txt or an install.py and document it here.
### Python 3.6
DExTer requires python version 3.6 or greater.
### pywin32 python package
This is required to access the DTE interface for the Visual Studio debuggers.
<python-executable> -m pip install pywin32
### clang
DExTer is current compatible with 'clang' and 'clang-cl' compiler drivers. The compiler must be available for DExTer, for example the following command should successfully build a runnable executable.
<compiler-executable> tests/nostdlib/fibonacci/test.cpp
## Running a test case
The following DExTer commands build the test.cpp from the tests/nostdlib/fibonacci directory and quietly runs it on the Visual Studio debugger, reporting the debug experience heuristic. The first command builds with no optimizations (/Od) and scores 1.0000. The second command builds with optimizations (/Ox) and scores 0.2832 which suggests a worse debugging experience.
dexter.py test --builder clang-cl_vs2015 --debugger vs2017 --cflags="/Od /Zi" --ldflags="/Zi" -- tests/nostdlib/fibonacci
fibonacci = (1.0000)
dexter.py test --builder clang-cl_vs2015 --debugger vs2017 --cflags="/Ox /Zi" --ldflags="/Zi" -- tests/nostdlib/fibonacci
fibonacci = (0.2832)
## An example test case
The sample test case (tests/nostdlib/fibonacci) looks like this:
1. #ifdef _MSC_VER
2. # define DEX_NOINLINE __declspec(noinline)
3. #else
4. # define DEX_NOINLINE __attribute__((__noinline__))
5. #endif
6.
7. DEX_NOINLINE
8. void Fibonacci(int terms, int& total)
9. {
0. int first = 0;
11. int second = 1;
12. for (int i = 0; i < terms; ++i)
13. {
14. int next = first + second; // DexLabel('start')
15. total += first;
16. first = second;
17. second = next; // DexLabel('end')
18. }
19. }
20.
21. int main()
22. {
23. int total = 0;
24. Fibonacci(5, total);
25. return total;
26. }
27.
28. /*
29. DexExpectWatchValue('i', '0', '1', '2', '3', '4',
30. from_line='start', to_line='end')
31. DexExpectWatchValue('first', '0', '1', '2', '3', '5',
32. from_line='start', to_line='end')
33. DexExpectWatchValue('second', '1', '2', '3', '5',
34 from_line='start', to_line='end')
35. DexExpectWatchValue('total', '0', '1', '2', '4', '7',
36. from_line='start', to_line='end')
37. DexExpectWatchValue('next', '1', '2', '3', '5', '8',
38. from_line='start', to_line='end')
39. DexExpectWatchValue('total', '7', on_line=25)
40. DexExpectStepKind('FUNC_EXTERNAL', 0)
41. */
[DexLabel][1] is used to give a name to a line number.
The [DexExpectWatchValue][2] command states that an expression, e.g. `i`, should
have particular values, `'0', '1', '2', '3','4'`, sequentially over the program
lifetime on particular lines. You can refer to a named line or simply the line
number (See line 39).
At the end of the test is the following line:
DexExpectStepKind('FUNC_EXTERNAL', 0)
This [DexExpectStepKind][3] command indicates that we do not expect the debugger
to step into a file outside of the test directory.
[1]: Commands.md#DexLabel
[2]: Commands.md#DexExpectWatchValue
[3]: Commands.md#DexExpectStepKind
## Detailed DExTer reports
Running the command below launches the tests/nostdlib/fibonacci test case in DExTer, using clang-cl as the compiler, Visual Studio 2017 as the debugger, and producing a detailed report:
$ dexter.py test --builder clang-cl_vs2015 --debugger vs2017 --cflags="/Ox /Zi" --ldflags="/Zi" -v -- tests/nostdlib/fibonacci
The detailed report is enabled by `-v` and shows a breakdown of the information from each debugger step. For example:
fibonacci = (0.2832)
## BEGIN ##
[1, "main", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 23, 1, "BREAKPOINT", "FUNC", {}]
[2, "main", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 24, 1, "BREAKPOINT", "VERTICAL_FORWARD", {}]
[3, "main", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 25, 1, "BREAKPOINT", "VERTICAL_FORWARD", {}]
. [4, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 13, 1, "BREAKPOINT", "FUNC", {}]
. [5, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 16, 1, "BREAKPOINT", "VERTICAL_FORWARD", {"i": "Variable is optimized away and not available.", "next": "Variable is optimized away and not available.", "second": "Variable is optimized away and not available.", "total": "0", "first": "Variable is optimized away and not available."}]
. [6, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 13, 1, "BREAKPOINT", "VERTICAL_BACKWARD", {}]
. [7, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 15, 1, "BREAKPOINT", "VERTICAL_FORWARD", {"i": "Variable is optimized away and not available.", "second": "Variable is optimized away and not available.", "total": "0", "first": "Variable is optimized away and not available."}]
. [8, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 16, 1, "BREAKPOINT", "VERTICAL_FORWARD", {"i": "Variable is optimized away and not available.", "next": "Variable is optimized away and not available.", "second": "Variable is optimized away and not available.", "total": "0", "first": "Variable is optimized away and not available."}]
. [9, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 15, 1, "BREAKPOINT", "VERTICAL_BACKWARD", {"i": "Variable is optimized away and not available.", "second": "1", "total": "0", "first": "0"}]
. [10, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 13, 1, "BREAKPOINT", "VERTICAL_BACKWARD", {}]
. [11, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 16, 1, "BREAKPOINT", "VERTICAL_FORWARD", {"i": "Variable is optimized away and not available.", "next": "Variable is optimized away and not available.", "second": "Variable is optimized away and not available.", "total": "0", "first": "Variable is optimized away and not available."}]
. [12, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 15, 1, "BREAKPOINT", "VERTICAL_BACKWARD", {"i": "Variable is optimized away and not available.", "second": "1", "total": "0", "first": "1"}]
. [13, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 13, 1, "BREAKPOINT", "VERTICAL_BACKWARD", {}]
. [14, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 16, 1, "BREAKPOINT", "VERTICAL_FORWARD", {"i": "Variable is optimized away and not available.", "next": "Variable is optimized away and not available.", "second": "Variable is optimized away and not available.", "total": "0", "first": "Variable is optimized away and not available."}]
. [15, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 15, 1, "BREAKPOINT", "VERTICAL_BACKWARD", {"i": "Variable is optimized away and not available.", "second": "2", "total": "0", "first": "1"}]
. [16, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 13, 1, "BREAKPOINT", "VERTICAL_BACKWARD", {}]
. [17, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 16, 1, "BREAKPOINT", "VERTICAL_FORWARD", {"i": "Variable is optimized away and not available.", "next": "Variable is optimized away and not available.", "second": "Variable is optimized away and not available.", "total": "0", "first": "Variable is optimized away and not available."}]
. [18, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 15, 1, "BREAKPOINT", "VERTICAL_BACKWARD", {"i": "Variable is optimized away and not available.", "second": "3", "total": "0", "first": "2"}]
. [19, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 13, 1, "BREAKPOINT", "VERTICAL_BACKWARD", {}]
. [20, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 16, 1, "BREAKPOINT", "VERTICAL_FORWARD", {"i": "Variable is optimized away and not available.", "next": "Variable is optimized away and not available.", "second": "Variable is optimized away and not available.", "total": "0", "first": "Variable is optimized away and not available."}]
. [21, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 15, 1, "BREAKPOINT", "VERTICAL_BACKWARD", {"i": "Variable is optimized away and not available.", "second": "5", "total": "0", "first": "3"}]
. [22, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 13, 1, "BREAKPOINT", "VERTICAL_BACKWARD", {}]
. [23, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 16, 1, "BREAKPOINT", "VERTICAL_FORWARD", {"i": "Variable is optimized away and not available.", "next": "Variable is optimized away and not available.", "second": "Variable is optimized away and not available.", "total": "0", "first": "Variable is optimized away and not available."}]
. [24, "Fibonacci", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 20, 1, "BREAKPOINT", "VERTICAL_FORWARD", {}]
[25, "main", "c:\\dexter\\tests\\nostdlib\\fibonacci\\test.cpp", 26, 1, "BREAKPOINT", "FUNC", {"total": "7"}]
## END (25 steps) ##
step kind differences [0/1]
FUNC_EXTERNAL:
0
test.cpp:15-18 [first] [9/21]
expected encountered values:
0
1
2
3
missing values:
5 [-6]
result optimized away:
step 5 (Variable is optimized away and not available.) [-3]
step 7 (Variable is optimized away and not available.)
step 8 (Variable is optimized away and not available.)
step 11 (Variable is optimized away and not available.)
step 14 (Variable is optimized away and not available.)
step 17 (Variable is optimized away and not available.)
step 20 (Variable is optimized away and not available.)
step 23 (Variable is optimized away and not available.)
test.cpp:15-18 [i] [15/21]
result optimized away:
step 5 (Variable is optimized away and not available.) [-3]
step 7 (Variable is optimized away and not available.) [-3]
step 8 (Variable is optimized away and not available.) [-3]
step 9 (Variable is optimized away and not available.) [-3]
step 11 (Variable is optimized away and not available.) [-3]
step 12 (Variable is optimized away and not available.)
step 14 (Variable is optimized away and not available.)
step 15 (Variable is optimized away and not available.)
step 17 (Variable is optimized away and not available.)
step 18 (Variable is optimized away and not available.)
step 20 (Variable is optimized away and not available.)
step 21 (Variable is optimized away and not available.)
step 23 (Variable is optimized away and not available.)
test.cpp:15-18 [second] [21/21]
expected encountered values:
1
2
3
5
result optimized away:
step 5 (Variable is optimized away and not available.) [-3]
step 7 (Variable is optimized away and not available.) [-3]
step 8 (Variable is optimized away and not available.) [-3]
step 11 (Variable is optimized away and not available.) [-3]
step 14 (Variable is optimized away and not available.) [-3]
step 17 (Variable is optimized away and not available.) [-3]
step 20 (Variable is optimized away and not available.) [-3]
step 23 (Variable is optimized away and not available.)
test.cpp:15-18 [total] [21/21]
expected encountered values:
0
missing values:
1 [-6]
2 [-6]
4 [-6]
7 [-3]
test.cpp:16-18 [next] [15/21]
result optimized away:
step 5 (Variable is optimized away and not available.) [-3]
step 8 (Variable is optimized away and not available.) [-3]
step 11 (Variable is optimized away and not available.) [-3]
step 14 (Variable is optimized away and not available.) [-3]
step 17 (Variable is optimized away and not available.) [-3]
step 20 (Variable is optimized away and not available.)
step 23 (Variable is optimized away and not available.)
test.cpp:26 [total] [0/7]
expected encountered values:
7
The first line
fibonacci = (0.2832)
shows a score of 0.2832 suggesting that unexpected behavior has been seen. This score is on scale of 0.0000 to 1.000, with 0.000 being the worst score possible and 1.000 being the best score possible. The verbose output shows the reason for any scoring. For example:
test.cpp:15-18 [first] [9/21]
expected encountered values:
0
1
2
3
missing values:
5 [-6]
result optimized away:
step 5 (Variable is optimized away and not available.) [-3]
step 7 (Variable is optimized away and not available.)
step 8 (Variable is optimized away and not available.)
step 11 (Variable is optimized away and not available.)
step 14 (Variable is optimized away and not available.)
step 17 (Variable is optimized away and not available.)
step 20 (Variable is optimized away and not available.)
step 23 (Variable is optimized away and not available.)
shows that for `first` the expected values 0, 1, 2 and 3 were seen, 5 was not. On some steps the variable was reported as being optimized away.
## Writing new test cases
Each test requires a `test.cfg` file. Currently the contents of this file are not read, but its presence is used to determine the root directory of a test. In the future, configuration variables for the test such as supported language modes may be stored in this file. Use the various [commands](Commands.md) to encode debugging expectations.
## Additional tools
For clang-based compilers, the `clang-opt-bisect` tool can be used to get a breakdown of which LLVM passes may be contributing to debugging experience issues. For example:
$ dexter.py clang-opt-bisect tests/nostdlib/fibonacci --builder clang-cl --debugger vs2017 --cflags="/Ox /Zi" --ldflags="/Zi"
pass 1/211 = (1.0000) (0.0000) [Simplify the CFG on function (?Fibonacci@@YAXHAEAH@Z)]
pass 2/211 = (0.7611) (-0.2389) [SROA on function (?Fibonacci@@YAXHAEAH@Z)]
pass 3/211 = (0.7611) (0.0000) [Early CSE on function (?Fibonacci@@YAXHAEAH@Z)]
pass 4/211 = (0.7611) (0.0000) [Simplify the CFG on function (main)]
pass 5/211 = (0.7611) (0.0000) [SROA on function (main)]
pass 6/211 = (0.7611) (0.0000) [Early CSE on function (main)]
pass 7/211 = (0.7611) (0.0000) [Infer set function attributes on module (c:\dexter\tests\fibonacci\test.cpp)]
pass 8/211 = (0.7611) (0.0000) [Interprocedural Sparse Conditional Constant Propagation on module (c:\dexter\tests\fibonacci\test.cpp)]
pass 9/211 = (0.7611) (0.0000) [Called Value Propagation on module (c:\dexter\tests\fibonacci\test.cpp)]
pass 10/211 = (0.7611) (0.0000) [Global Variable Optimizer on module (c:\dexter\tests\fibonacci\test.cpp)]
pass 11/211 = (0.7611) (0.0000) [Promote Memory to Register on function (?Fibonacci@@YAXHAEAH@Z)]
pass 12/211 = (0.7611) (0.0000) [Promote Memory to Register on function (main)]
pass 13/211 = (0.7611) (0.0000) [Dead Argument Elimination on module (c:\dexter\tests\fibonacci\test.cpp)]
pass 14/211 = (0.7611) (0.0000) [Combine redundant instructions on function (?Fibonacci@@YAXHAEAH@Z)]
pass 15/211 = (0.7611) (0.0000) [Simplify the CFG on function (?Fibonacci@@YAXHAEAH@Z)]a
pass 16/211 = (0.7345) (-0.0265) [Combine redundant instructions on function (main)]
pass 17/211 = (0.7345) (0.0000) [Simplify the CFG on function (main)]
pass 18/211 = (0.7345) (0.0000) [Remove unused exception handling info on SCC (?Fibonacci@@YAXHAEAH@Z)]
pass 19/211 = (0.7345) (0.0000) [Function Integration/Inlining on SCC (?Fibonacci@@YAXHAEAH@Z)]
pass 20/211 = (0.7345) (0.0000) [Deduce function attributes on SCC (?Fibonacci@@YAXHAEAH@Z)]
pass 21/211 = (0.7345) (0.0000) [SROA on function (?Fibonacci@@YAXHAEAH@Z)]
pass 22/211 = (0.7345) (0.0000) [Early CSE w/ MemorySSA on function (?Fibonacci@@YAXHAEAH@Z)]
pass 23/211 = (0.7345) (0.0000) [Speculatively execute instructions if target has divergent branches on function (?Fibonacci@@YAXHAEAH@Z)]
pass 24/211 = (0.7345) (0.0000) [Jump Threading on function (?Fibonacci@@YAXHAEAH@Z)]
pass 25/211 = (0.7345) (0.0000) [Value Propagation on function (?Fibonacci@@YAXHAEAH@Z)]
pass 26/211 = (0.7345) (0.0000) [Simplify the CFG on function (?Fibonacci@@YAXHAEAH@Z)]
pass 27/211 = (0.7345) (0.0000) [Combine redundant instructions on function (?Fibonacci@@YAXHAEAH@Z)]
pass 28/211 = (0.7345) (0.0000) [Tail Call Elimination on function (?Fibonacci@@YAXHAEAH@Z)]
pass 29/211 = (0.7345) (0.0000) [Simplify the CFG on function (?Fibonacci@@YAXHAEAH@Z)]
pass 30/211 = (0.7345) (0.0000) [Reassociate expressions on function (?Fibonacci@@YAXHAEAH@Z)]
pass 31/211 = (0.8673) (0.1327) [Rotate Loops on loop]
pass 32/211 = (0.5575) (-0.3097) [Loop Invariant Code Motion on loop]
pass 33/211 = (0.5575) (0.0000) [Unswitch loops on loop]
pass 34/211 = (0.5575) (0.0000) [Simplify the CFG on function (?Fibonacci@@YAXHAEAH@Z)]
pass 35/211 = (0.5575) (0.0000) [Combine redundant instructions on function (?Fibonacci@@YAXHAEAH@Z)]
pass 36/211 = (0.5575) (0.0000) [Induction Variable Simplification on loop]
pass 37/211 = (0.5575) (0.0000) [Recognize loop idioms on loop]
<output-snipped>

View File

@ -1,8 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
__version__ = '1.0.0'

View File

@ -1,117 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Deals with the processing execution of shell or batch build scripts."""
import os
import subprocess
import unittest
from dex.dextIR import BuilderIR
from dex.utils import Timer
from dex.utils.Exceptions import BuildScriptException
def _quotify(text):
if '"' in text or ' ' not in text:
return text
return '"{}"'.format(text)
def _get_script_environment(source_files, compiler_options,
linker_options, executable_file):
source_files = [_quotify(f) for f in source_files]
object_files = [
_quotify('{}.o'.format(os.path.basename(f))) for f in source_files
]
source_indexes = ['{:02d}'.format(i + 1) for i in range(len(source_files))]
env_variables = {}
env_variables['SOURCE_INDEXES'] = ' '.join(source_indexes)
env_variables['SOURCE_FILES'] = ' '.join(source_files)
env_variables['OBJECT_FILES'] = ' '.join(object_files)
env_variables['LINKER_OPTIONS'] = linker_options
for i, _ in enumerate(source_files):
index = source_indexes[i]
env_variables['SOURCE_FILE_{}'.format(index)] = source_files[i]
env_variables['OBJECT_FILE_{}'.format(index)] = object_files[i]
env_variables['COMPILER_OPTIONS_{}'.format(index)] = compiler_options[i]
env_variables['EXECUTABLE_FILE'] = executable_file
return env_variables
def run_external_build_script(context, script_path, source_files,
compiler_options, linker_options,
executable_file):
"""Build an executable using a builder script.
The executable is saved to `context.working_directory.path`.
Returns:
( stdout (str), stderr (str), builder (BuilderIR) )
"""
builderIR = BuilderIR(
name=context.options.builder,
cflags=compiler_options,
ldflags=linker_options,
)
assert len(source_files) == len(compiler_options), (source_files,
compiler_options)
script_environ = _get_script_environment(source_files, compiler_options,
linker_options, executable_file)
env = dict(os.environ)
env.update(script_environ)
try:
with Timer('running build script'):
process = subprocess.Popen(
[script_path],
cwd=context.working_directory.path,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = process.communicate()
returncode = process.returncode
if returncode != 0:
raise BuildScriptException(
'{}: failed with returncode {}.\nstdout:\n{}\n\nstderr:\n{}\n'.
format(script_path, returncode, out, err),
script_error=err)
return out.decode('utf-8'), err.decode('utf-8'), builderIR
except OSError as e:
raise BuildScriptException('{}: {}'.format(e.strerror, script_path))
class TestBuilder(unittest.TestCase):
def test_get_script_environment(self):
source_files = ['a.a', 'b.b']
compiler_options = ['-option1 value1', '-option2 value2']
linker_options = '-optionX valueX'
executable_file = 'exe.exe'
env = _get_script_environment(source_files, compiler_options,
linker_options, executable_file)
assert env['SOURCE_FILES'] == 'a.a b.b'
assert env['OBJECT_FILES'] == 'a.a.o b.b.o'
assert env['SOURCE_INDEXES'] == '01 02'
assert env['LINKER_OPTIONS'] == '-optionX valueX'
assert env['SOURCE_FILE_01'] == 'a.a'
assert env['SOURCE_FILE_02'] == 'b.b'
assert env['OBJECT_FILE_01'] == 'a.a.o'
assert env['OBJECT_FILE_02'] == 'b.b.o'
assert env['EXECUTABLE_FILE'] == 'exe.exe'
assert env['COMPILER_OPTIONS_01'] == '-option1 value1'
assert env['COMPILER_OPTIONS_02'] == '-option2 value2'

View File

@ -1,56 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Command line options for subtools that use the builder component."""
import os
from dex.tools import Context
from dex.utils import is_native_windows
def _find_build_scripts():
"""Finds build scripts in the 'scripts' subdirectory.
Returns:
{ script_name (str): directory (str) }
"""
try:
return _find_build_scripts.cached
except AttributeError:
scripts_directory = os.path.join(os.path.dirname(__file__), 'scripts')
if is_native_windows():
scripts_directory = os.path.join(scripts_directory, 'windows')
else:
scripts_directory = os.path.join(scripts_directory, 'posix')
assert os.path.isdir(scripts_directory), scripts_directory
results = {}
for f in os.listdir(scripts_directory):
results[os.path.splitext(f)[0]] = os.path.abspath(
os.path.join(scripts_directory, f))
_find_build_scripts.cached = results
return results
def add_builder_tool_arguments(parser):
parser.add_argument('--binary',
metavar="<file>",
help='provide binary file to override --builder')
parser.add_argument(
'--builder',
type=str,
choices=sorted(_find_build_scripts().keys()),
help='test builder to use')
parser.add_argument(
'--cflags', type=str, default='', help='compiler flags')
parser.add_argument('--ldflags', type=str, default='', help='linker flags')
def handle_builder_tool_options(context: Context) -> str:
return _find_build_scripts()[context.options.builder]

View File

@ -1,10 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.builder.Builder import run_external_build_script
from dex.builder.ParserOptions import add_builder_tool_arguments
from dex.builder.ParserOptions import handle_builder_tool_options

View File

@ -1,16 +0,0 @@
#!/usr/bin/env bash
set -e
if test -z "$PATHTOCLANG"; then
PATHTOCLANG=clang
fi
for INDEX in $SOURCE_INDEXES
do
CFLAGS=$(eval echo "\$COMPILER_OPTIONS_$INDEX")
SRCFILE=$(eval echo "\$SOURCE_FILE_$INDEX")
OBJFILE=$(eval echo "\$OBJECT_FILE_$INDEX")
$PATHTOCLANG -std=gnu11 -c $CFLAGS $SRCFILE -o $OBJFILE
done
$PATHTOCLANG $LINKER_OPTIONS $OBJECT_FILES -o $EXECUTABLE_FILE

View File

@ -1,16 +0,0 @@
#!/usr/bin/env bash
set -e
if test -z "$PATHTOCLANGPP"; then
PATHTOCLANGPP=clang++
fi
for INDEX in $SOURCE_INDEXES
do
CFLAGS=$(eval echo "\$COMPILER_OPTIONS_$INDEX")
SRCFILE=$(eval echo "\$SOURCE_FILE_$INDEX")
OBJFILE=$(eval echo "\$OBJECT_FILE_$INDEX")
$PATHTOCLANGPP -std=gnu++11 -c $CFLAGS $SRCFILE -o $OBJFILE
done
$PATHTOCLANGPP $LINKER_OPTIONS $OBJECT_FILES -o $EXECUTABLE_FILE

View File

@ -1,23 +0,0 @@
@echo OFF
setlocal EnableDelayedExpansion
call "%VS140COMNTOOLS%..\..\VC\bin\amd64\vcvars64.bat"
@echo OFF
setlocal EnableDelayedExpansion
for %%I in (%SOURCE_INDEXES%) do (
%PATHTOCLANGCL% /c !COMPILER_OPTIONS_%%I! !SOURCE_FILE_%%I! /Fo!OBJECT_FILE_%%I!
if errorlevel 1 goto :FAIL
)
%PATHTOCLANGCL% %LINKER_OPTIONS% %OBJECT_FILES% /Fe%EXECUTABLE_FILE%
if errorlevel 1 goto :FAIL
goto :END
:FAIL
echo FAILED
exit /B 1
:END
exit /B 0

View File

@ -1,17 +0,0 @@
setlocal EnableDelayedExpansion
for %%I in (%SOURCE_INDEXES%) do (
%PATHTOCLANGPP% -fuse-ld=lld -c !COMPILER_OPTIONS_%%I! !SOURCE_FILE_%%I! -o !OBJECT_FILE_%%I!
if errorlevel 1 goto :FAIL
)
%PATHTOCLANGPP% -fuse-ld=lld %LINKER_OPTIONS% %OBJECT_FILES% -o %EXECUTABLE_FILE%
if errorlevel 1 goto :FAIL
goto :END
:FAIL
echo FAILED
exit /B 1
:END
exit /B 0

View File

@ -1,54 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Base class for all DExTer commands, where a command is a specific Python
function that can be embedded into a comment in the source code under test
which will then be executed by DExTer during debugging.
"""
import abc
from typing import List
class CommandBase(object, metaclass=abc.ABCMeta):
def __init__(self):
self.path = None
self.lineno = None
self.raw_text = ''
def get_label_args(self):
return list()
def has_labels(self):
return False
@abc.abstractstaticmethod
def get_name():
"""This abstract method is usually implemented in subclasses as:
return __class__.__name__
"""
def get_watches(self) -> List[str]:
return []
@abc.abstractmethod
def eval(self):
"""Evaluate the command.
This will be called when constructing a Heuristic object to determine
the debug score.
Returns:
The logic for handling the result of CommandBase.eval() must be
defined in Heuristic.__init__() so a consitent return type between
commands is not enforced.
"""
@staticmethod
def get_subcommands() -> dict:
"""Returns a dictionary of subcommands in the form {name: command} or
None if no subcommands are required.
"""
return None

View File

@ -1,421 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Parse a DExTer command. In particular, ensure that only a very limited
subset of Python is allowed, in order to prevent the possibility of unsafe
Python code being embedded within DExTer commands.
"""
import os
import unittest
from copy import copy
from collections import defaultdict
from dex.utils.Exceptions import CommandParseError
from dex.command.CommandBase import CommandBase
from dex.command.commands.DexExpectProgramState import DexExpectProgramState
from dex.command.commands.DexExpectStepKind import DexExpectStepKind
from dex.command.commands.DexExpectStepOrder import DexExpectStepOrder
from dex.command.commands.DexExpectWatchType import DexExpectWatchType
from dex.command.commands.DexExpectWatchValue import DexExpectWatchValue
from dex.command.commands.DexLabel import DexLabel
from dex.command.commands.DexUnreachable import DexUnreachable
from dex.command.commands.DexWatch import DexWatch
def _get_valid_commands():
"""Return all top level DExTer test commands.
Returns:
{ name (str): command (class) }
"""
return {
DexExpectProgramState.get_name() : DexExpectProgramState,
DexExpectStepKind.get_name() : DexExpectStepKind,
DexExpectStepOrder.get_name() : DexExpectStepOrder,
DexExpectWatchType.get_name() : DexExpectWatchType,
DexExpectWatchValue.get_name() : DexExpectWatchValue,
DexLabel.get_name() : DexLabel,
DexUnreachable.get_name() : DexUnreachable,
DexWatch.get_name() : DexWatch
}
def _get_command_name(command_raw: str) -> str:
"""Return command name by splitting up DExTer command contained in
command_raw on the first opening paranthesis and further stripping
any potential leading or trailing whitespace.
"""
return command_raw.split('(', 1)[0].rstrip()
def _merge_subcommands(command_name: str, valid_commands: dict) -> dict:
"""Merge valid_commands and command_name's subcommands into a new dict.
Returns:
{ name (str): command (class) }
"""
subcommands = valid_commands[command_name].get_subcommands()
if subcommands:
return { **valid_commands, **subcommands }
return valid_commands
def _build_command(command_type, raw_text: str, path: str, lineno: str) -> CommandBase:
"""Build a command object from raw text.
This function will call eval().
Raises:
Any exception that eval() can raise.
Returns:
A dexter command object.
"""
valid_commands = _merge_subcommands(
command_type.get_name(), { command_type.get_name(): command_type })
# pylint: disable=eval-used
command = eval(raw_text, valid_commands)
# pylint: enable=eval-used
command.raw_text = raw_text
command.path = path
command.lineno = lineno
return command
def resolve_labels(command: CommandBase, commands: dict):
"""Attempt to resolve any labels in command"""
dex_labels = commands['DexLabel']
command_label_args = command.get_label_args()
for command_arg in command_label_args:
for dex_label in list(dex_labels.values()):
if (os.path.samefile(dex_label.path, command.path) and
dex_label.eval() == command_arg):
command.resolve_label(dex_label.get_as_pair())
# labels for command should be resolved by this point.
if command.has_labels():
syntax_error = SyntaxError()
syntax_error.filename = command.path
syntax_error.lineno = command.lineno
syntax_error.offset = 0
syntax_error.msg = 'Unresolved labels'
for label in command.get_label_args():
syntax_error.msg += ' \'' + label + '\''
raise syntax_error
def _search_line_for_cmd_start(line: str, start: int, valid_commands: dict) -> int:
"""Scan `line` for a string matching any key in `valid_commands`.
Start searching from `start`.
Commands escaped with `\` (E.g. `\DexLabel('a')`) are ignored.
Returns:
int: the index of the first character of the matching string in `line`
or -1 if no command is found.
"""
for command in valid_commands:
idx = line.find(command, start)
if idx != -1:
# Ignore escaped '\' commands.
if idx > 0 and line[idx - 1] == '\\':
continue
return idx
return -1
def _search_line_for_cmd_end(line: str, start: int, paren_balance: int) -> (int, int):
"""Find the end of a command by looking for balanced parentheses.
Args:
line: String to scan.
start: Index into `line` to start looking.
paren_balance(int): paren_balance after previous call.
Note:
On the first call `start` should point at the opening parenthesis and
`paren_balance` should be set to 0. Subsequent calls should pass in the
returned `paren_balance`.
Returns:
( end, paren_balance )
Where end is 1 + the index of the last char in the command or, if the
parentheses are not balanced, the end of the line.
paren_balance will be 0 when the parentheses are balanced.
"""
for end in range(start, len(line)):
ch = line[end]
if ch == '(':
paren_balance += 1
elif ch == ')':
paren_balance -=1
if paren_balance == 0:
break
end += 1
return (end, paren_balance)
class TextPoint():
def __init__(self, line, char):
self.line = line
self.char = char
def get_lineno(self):
return self.line + 1
def get_column(self):
return self.char + 1
def format_parse_err(msg: str, path: str, lines: list, point: TextPoint) -> CommandParseError:
err = CommandParseError()
err.filename = path
err.src = lines[point.line].rstrip()
err.lineno = point.get_lineno()
err.info = msg
err.caret = '{}<r>^</>'.format(' ' * (point.char))
return err
def skip_horizontal_whitespace(line, point):
for idx, char in enumerate(line[point.char:]):
if char not in ' \t':
point.char += idx
return
def _find_all_commands_in_file(path, file_lines, valid_commands):
commands = defaultdict(dict)
paren_balance = 0
region_start = TextPoint(0, 0)
for region_start.line in range(len(file_lines)):
line = file_lines[region_start.line]
region_start.char = 0
# Search this line till we find no more commands.
while True:
# If parens are currently balanced we can look for a new command.
if paren_balance == 0:
region_start.char = _search_line_for_cmd_start(line, region_start.char, valid_commands)
if region_start.char == -1:
break # Read next line.
command_name = _get_command_name(line[region_start.char:])
cmd_point = copy(region_start)
cmd_text_list = [command_name]
region_start.char += len(command_name) # Start searching for parens after cmd.
skip_horizontal_whitespace(line, region_start)
if region_start.char >= len(line) or line[region_start.char] != '(':
raise format_parse_err(
"Missing open parenthesis", path, file_lines, region_start)
end, paren_balance = _search_line_for_cmd_end(line, region_start.char, paren_balance)
# Add this text blob to the command.
cmd_text_list.append(line[region_start.char:end])
# Move parse ptr to end of line or parens
region_start.char = end
# If the parens are unbalanced start reading the next line in an attempt
# to find the end of the command.
if paren_balance != 0:
break # Read next line.
# Parens are balanced, we have a full command to evaluate.
raw_text = "".join(cmd_text_list)
try:
command = _build_command(
valid_commands[command_name],
raw_text,
path,
cmd_point.get_lineno(),
)
except SyntaxError as e:
# This err should point to the problem line.
err_point = copy(cmd_point)
# To e the command start is the absolute start, so use as offset.
err_point.line += e.lineno - 1 # e.lineno is a position, not index.
err_point.char += e.offset - 1 # e.offset is a position, not index.
raise format_parse_err(e.msg, path, file_lines, err_point)
except TypeError as e:
# This err should always point to the end of the command name.
err_point = copy(cmd_point)
err_point.char += len(command_name)
raise format_parse_err(str(e), path, file_lines, err_point)
else:
resolve_labels(command, commands)
assert (path, cmd_point) not in commands[command_name], (
command_name, commands[command_name])
commands[command_name][path, cmd_point] = command
if paren_balance != 0:
# This err should always point to the end of the command name.
err_point = copy(cmd_point)
err_point.char += len(command_name)
msg = "Unbalanced parenthesis starting here"
raise format_parse_err(msg, path, file_lines, err_point)
return dict(commands)
def find_all_commands(source_files):
commands = defaultdict(dict)
valid_commands = _get_valid_commands()
for source_file in source_files:
with open(source_file) as fp:
lines = fp.readlines()
file_commands = _find_all_commands_in_file(source_file, lines,
valid_commands)
for command_name in file_commands:
commands[command_name].update(file_commands[command_name])
return dict(commands)
class TestParseCommand(unittest.TestCase):
class MockCmd(CommandBase):
"""A mock DExTer command for testing parsing.
Args:
value (str): Unique name for this instance.
"""
def __init__(self, *args):
self.value = args[0]
def get_name():
return __class__.__name__
def eval(this):
pass
def __init__(self, *args):
super().__init__(*args)
self.valid_commands = {
TestParseCommand.MockCmd.get_name() : TestParseCommand.MockCmd
}
def _find_all_commands_in_lines(self, lines):
"""Use DExTer parsing methods to find all the mock commands in lines.
Returns:
{ cmd_name: { (path, line): command_obj } }
"""
return _find_all_commands_in_file(__file__, lines, self.valid_commands)
def _find_all_mock_values_in_lines(self, lines):
"""Use DExTer parsing methods to find all mock command values in lines.
Returns:
values (list(str)): MockCmd values found in lines.
"""
cmds = self._find_all_commands_in_lines(lines)
mocks = cmds.get(TestParseCommand.MockCmd.get_name(), None)
return [v.value for v in mocks.values()] if mocks else []
def test_parse_inline(self):
"""Commands can be embedded in other text."""
lines = [
'MockCmd("START") Lorem ipsum dolor sit amet, consectetur\n',
'adipiscing elit, MockCmd("EMBEDDED") sed doeiusmod tempor,\n',
'incididunt ut labore et dolore magna aliqua.\n'
]
values = self._find_all_mock_values_in_lines(lines)
self.assertTrue('START' in values)
self.assertTrue('EMBEDDED' in values)
def test_parse_multi_line_comment(self):
"""Multi-line commands can embed comments."""
lines = [
'Lorem ipsum dolor sit amet, consectetur\n',
'adipiscing elit, sed doeiusmod tempor,\n',
'incididunt ut labore et MockCmd(\n',
' "WITH_COMMENT" # THIS IS A COMMENT\n',
') dolore magna aliqua. Ut enim ad minim\n',
]
values = self._find_all_mock_values_in_lines(lines)
self.assertTrue('WITH_COMMENT' in values)
def test_parse_empty(self):
"""Empty files are silently ignored."""
lines = []
values = self._find_all_mock_values_in_lines(lines)
self.assertTrue(len(values) == 0)
def test_parse_bad_whitespace(self):
"""Throw exception when parsing badly formed whitespace."""
lines = [
'MockCmd\n',
'("XFAIL_CMD_LF_PAREN")\n',
]
with self.assertRaises(CommandParseError):
values = self._find_all_mock_values_in_lines(lines)
def test_parse_good_whitespace(self):
"""Try to emulate python whitespace rules"""
lines = [
'MockCmd("NONE")\n',
'MockCmd ("SPACE")\n',
'MockCmd\t\t("TABS")\n',
'MockCmd( "ARG_SPACE" )\n',
'MockCmd(\t\t"ARG_TABS"\t\t)\n',
'MockCmd(\n',
'"CMD_PAREN_LF")\n',
]
values = self._find_all_mock_values_in_lines(lines)
self.assertTrue('NONE' in values)
self.assertTrue('SPACE' in values)
self.assertTrue('TABS' in values)
self.assertTrue('ARG_SPACE' in values)
self.assertTrue('ARG_TABS' in values)
self.assertTrue('CMD_PAREN_LF' in values)
def test_parse_share_line(self):
"""More than one command can appear on one line."""
lines = [
'MockCmd("START") MockCmd("CONSECUTIVE") words '
'MockCmd("EMBEDDED") more words\n'
]
values = self._find_all_mock_values_in_lines(lines)
self.assertTrue('START' in values)
self.assertTrue('CONSECUTIVE' in values)
self.assertTrue('EMBEDDED' in values)
def test_parse_escaped(self):
"""Escaped commands are ignored."""
lines = [
'words \MockCmd("IGNORED") words words words\n'
]
values = self._find_all_mock_values_in_lines(lines)
self.assertFalse('IGNORED' in values)

View File

@ -1,23 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
class StepValueInfo(object):
def __init__(self, step_index, watch_info, expected_value):
self.step_index = step_index
self.watch_info = watch_info
self.expected_value = expected_value
def __str__(self):
return '{}:{}: expected value:{}'.format(self.step_index, self.watch_info, self.expected_value)
def __eq__(self, other):
return (self.watch_info.expression == other.watch_info.expression
and self.expected_value == other.expected_value)
def __hash__(self):
return hash(self.watch_info.expression, self.expected_value)

View File

@ -1,9 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.command.ParseCommand import find_all_commands
from dex.command.StepValueInfo import StepValueInfo

View File

@ -1,83 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Command for specifying a partial or complete state for the program to enter
during execution.
"""
from itertools import chain
from dex.command.CommandBase import CommandBase
from dex.dextIR import ProgramState, SourceLocation, StackFrame, DextIR
def frame_from_dict(source: dict) -> StackFrame:
if 'location' in source:
assert isinstance(source['location'], dict)
source['location'] = SourceLocation(**source['location'])
return StackFrame(**source)
def state_from_dict(source: dict) -> ProgramState:
if 'frames' in source:
assert isinstance(source['frames'], list)
source['frames'] = list(map(frame_from_dict, source['frames']))
return ProgramState(**source)
class DexExpectProgramState(CommandBase):
"""Expect to see a given program `state` a certain numer of `times`.
DexExpectProgramState(state [,**times])
See Commands.md for more info.
"""
def __init__(self, *args, **kwargs):
if len(args) != 1:
raise TypeError('expected exactly one unnamed arg')
self.program_state_text = str(args[0])
self.expected_program_state = state_from_dict(args[0])
self.times = kwargs.pop('times', -1)
if kwargs:
raise TypeError('unexpected named args: {}'.format(
', '.join(kwargs)))
# Step indices at which the expected program state was encountered.
self.encounters = []
super(DexExpectProgramState, self).__init__()
@staticmethod
def get_name():
return __class__.__name__
def get_watches(self):
frame_expects = chain.from_iterable(frame.watches
for frame in self.expected_program_state.frames)
return set(frame_expects)
def eval(self, step_collection: DextIR) -> bool:
for step in step_collection.steps:
if self.expected_program_state.match(step.program_state):
self.encounters.append(step.step_index)
return self.times < 0 < len(self.encounters) or len(self.encounters) == self.times
def has_labels(self):
return len(self.get_label_args()) > 0
def get_label_args(self):
return [frame.location.lineno
for frame in self.expected_program_state.frames
if frame.location and
isinstance(frame.location.lineno, str)]
def resolve_label(self, label_line__pair):
label, line = label_line__pair
for frame in self.expected_program_state.frames:
if frame.location and frame.location.lineno == label:
frame.location.lineno = line

View File

@ -1,45 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Command for specifying an expected number of steps of a particular kind."""
from dex.command.CommandBase import CommandBase
from dex.dextIR.StepIR import StepKind
class DexExpectStepKind(CommandBase):
"""Expect to see a particular step `kind` a number of `times` while stepping
through the program.
DexExpectStepKind(kind, times)
See Commands.md for more info.
"""
def __init__(self, *args):
if len(args) != 2:
raise TypeError('expected two args')
try:
step_kind = StepKind[args[0]]
except KeyError:
raise TypeError('expected arg 0 to be one of {}'.format(
[kind for kind, _ in StepKind.__members__.items()]))
self.name = step_kind
self.count = args[1]
super(DexExpectStepKind, self).__init__()
@staticmethod
def get_name():
return __class__.__name__
def eval(self):
# DexExpectStepKind eval() implementation is mixed into
# Heuristic.__init__()
# [TODO] Fix this ^.
pass

View File

@ -1,39 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.command.CommandBase import CommandBase
from dex.dextIR import ValueIR
class DexExpectStepOrder(CommandBase):
"""Expect the line every `DexExpectStepOrder` is found on to be stepped on
in `order`. Each instance must have a set of unique ascending indicies.
DexExpectStepOrder(*order)
See Commands.md for more info.
"""
def __init__(self, *args):
if not args:
raise TypeError('Need at least one order number')
self.sequence = [int(x) for x in args]
super(DexExpectStepOrder, self).__init__()
@staticmethod
def get_name():
return __class__.__name__
def eval(self, debugger):
step_info = debugger.get_step_info()
loc = step_info.current_location
return {'DexExpectStepOrder': ValueIR(expression=str(loc.lineno),
value=str(debugger.step_index), type_name=None,
error_string=None,
could_evaluate=True,
is_optimized_away=True,
is_irretrievable=False)}

View File

@ -1,197 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""DexExpectWatch base class, holds logic for how to build and process expected
watch commands.
"""
import abc
import difflib
import os
from dex.command.CommandBase import CommandBase
from dex.command.StepValueInfo import StepValueInfo
class DexExpectWatchBase(CommandBase):
def __init__(self, *args, **kwargs):
if len(args) < 2:
raise TypeError('expected at least two args')
self.expression = args[0]
self.values = [str(arg) for arg in args[1:]]
try:
on_line = kwargs.pop('on_line')
self._from_line = on_line
self._to_line = on_line
except KeyError:
self._from_line = kwargs.pop('from_line', 1)
self._to_line = kwargs.pop('to_line', 999999)
self._require_in_order = kwargs.pop('require_in_order', True)
if kwargs:
raise TypeError('unexpected named args: {}'.format(
', '.join(kwargs)))
# Number of times that this watch has been encountered.
self.times_encountered = 0
# We'll pop from this set as we encounter values so anything left at
# the end can be considered as not having been seen.
self._missing_values = set(self.values)
self.misordered_watches = []
# List of StepValueInfos for any watch that is encountered as invalid.
self.invalid_watches = []
# List of StepValueInfo any any watch where we couldn't retrieve its
# data.
self.irretrievable_watches = []
# List of StepValueInfos for any watch that is encountered as having
# been optimized out.
self.optimized_out_watches = []
# List of StepValueInfos for any watch that is encountered that has an
# expected value.
self.expected_watches = []
# List of StepValueInfos for any watch that is encountered that has an
# unexpected value.
self.unexpected_watches = []
super(DexExpectWatchBase, self).__init__()
def get_watches(self):
return [self.expression]
@property
def line_range(self):
return list(range(self._from_line, self._to_line + 1))
@property
def missing_values(self):
return sorted(list(self._missing_values))
@property
def encountered_values(self):
return sorted(list(set(self.values) - self._missing_values))
def resolve_label(self, label_line_pair):
# from_line and to_line could have the same label.
label, lineno = label_line_pair
if self._to_line == label:
self._to_line = lineno
if self._from_line == label:
self._from_line = lineno
def has_labels(self):
return len(self.get_label_args()) > 0
def get_label_args(self):
return [label for label in (self._from_line, self._to_line)
if isinstance(label, str)]
@abc.abstractmethod
def _get_expected_field(self, watch):
"""Return a field from watch that this ExpectWatch command is checking.
"""
def _handle_watch(self, step_info):
self.times_encountered += 1
if not step_info.watch_info.could_evaluate:
self.invalid_watches.append(step_info)
return
if step_info.watch_info.is_optimized_away:
self.optimized_out_watches.append(step_info)
return
if step_info.watch_info.is_irretrievable:
self.irretrievable_watches.append(step_info)
return
if step_info.expected_value not in self.values:
self.unexpected_watches.append(step_info)
return
self.expected_watches.append(step_info)
try:
self._missing_values.remove(step_info.expected_value)
except KeyError:
pass
def _check_watch_order(self, actual_watches, expected_values):
"""Use difflib to figure out whether the values are in the expected order
or not.
"""
differences = []
actual_values = [w.expected_value for w in actual_watches]
value_differences = list(difflib.Differ().compare(actual_values,
expected_values))
missing_value = False
index = 0
for vd in value_differences:
kind = vd[0]
if kind == '+':
# A value that is encountered in the expected list but not in the
# actual list. We'll keep a note that something is wrong and flag
# the next value that matches as misordered.
missing_value = True
elif kind == ' ':
# This value is as expected. It might still be wrong if we've
# previously encountered a value that is in the expected list but
# not the actual list.
if missing_value:
missing_value = False
differences.append(actual_watches[index])
index += 1
elif kind == '-':
# A value that is encountered in the actual list but not the
# expected list.
differences.append(actual_watches[index])
index += 1
else:
assert False, 'unexpected diff:{}'.format(vd)
return differences
def eval(self, step_collection):
for step in step_collection.steps:
loc = step.current_location
if (os.path.exists(loc.path) and os.path.exists(self.path) and
os.path.samefile(loc.path, self.path) and
loc.lineno in self.line_range):
try:
watch = step.program_state.frames[0].watches[self.expression]
except KeyError:
pass
else:
expected_field = self._get_expected_field(watch)
step_info = StepValueInfo(step.step_index, watch,
expected_field)
self._handle_watch(step_info)
if self._require_in_order:
# A list of all watches where the value has changed.
value_change_watches = []
prev_value = None
for watch in self.expected_watches:
if watch.expected_value != prev_value:
value_change_watches.append(watch)
prev_value = watch.expected_value
self.misordered_watches = self._check_watch_order(
value_change_watches, [
v for v in self.values if v in
[w.expected_value for w in self.expected_watches]
])

View File

@ -1,26 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Command for specifying an expected set of types for a particular watch."""
from dex.command.commands.DexExpectWatchBase import DexExpectWatchBase
class DexExpectWatchType(DexExpectWatchBase):
"""Expect the expression `expr` to evaluate be evaluated and have each
evaluation's type checked against the list of `types`.
DexExpectWatchType(expr, *types [,**from_line=1][,**to_line=Max]
[,**on_line])
See Commands.md for more info.
"""
@staticmethod
def get_name():
return __class__.__name__
def _get_expected_field(self, watch):
return watch.type_name

View File

@ -1,27 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Command for specifying an expected set of values for a particular watch."""
from dex.command.commands.DexExpectWatchBase import DexExpectWatchBase
class DexExpectWatchValue(DexExpectWatchBase):
"""Expect the expression `expr` to evaluate to the list of `values`
sequentially.
DexExpectWatchValue(expr, *values [,**from_line=1][,**to_line=Max]
[,**on_line])
See Commands.md for more info.
"""
@staticmethod
def get_name():
return __class__.__name__
def _get_expected_field(self, watch):
return watch.value

View File

@ -1,31 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Command used to give a line in a test a named psuedonym. Every DexLabel has
a line number and Label string component.
"""
from dex.command.CommandBase import CommandBase
class DexLabel(CommandBase):
def __init__(self, label):
if not isinstance(label, str):
raise TypeError('invalid argument type')
self._label = label
super(DexLabel, self).__init__()
def get_as_pair(self):
return (self._label, self.lineno)
@staticmethod
def get_name():
return __class__.__name__
def eval(self):
return self._label

View File

@ -1,38 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.command.CommandBase import CommandBase
from dex.dextIR import ValueIR
class DexUnreachable(CommandBase):
"""Expect the source line this is found on will never be stepped on to.
DexUnreachable()
See Commands.md for more info.
"""
def __init(self):
super(DexUnreachable, self).__init__()
pass
@staticmethod
def get_name():
return __class__.__name__
def eval(self, debugger):
# If we're ever called, at all, then we're evaluating a line that has
# been marked as unreachable. Which means a failure.
vir = ValueIR(expression="Unreachable",
value="True", type_name=None,
error_string=None,
could_evaluate=True,
is_optimized_away=True,
is_irretrievable=False)
return {'DexUnreachable' : vir}

View File

@ -1,39 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Command to instruct the debugger to inspect the value of some set of
expressions on the current source line.
"""
from dex.command.CommandBase import CommandBase
class DexWatch(CommandBase):
"""[Deprecated] Evaluate each given `expression` when the debugger steps onto the
line this command is found on
DexWatch(*expressions)
See Commands.md for more info.
"""
def __init__(self, *args):
if not args:
raise TypeError('expected some arguments')
for arg in args:
if not isinstance(arg, str):
raise TypeError('invalid argument type')
self._args = args
super(DexWatch, self).__init__()
@staticmethod
def get_name():
return __class__.__name__
def eval(self, debugger):
return {arg: debugger.evaluate_expression(arg) for arg in self._args}

View File

@ -1,227 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Base class for all debugger interface implementations."""
import abc
from itertools import chain
import os
import sys
import time
import traceback
from dex.dextIR import DebuggerIR, ValueIR
from dex.utils.Exceptions import DebuggerException
from dex.utils.Exceptions import NotYetLoadedDebuggerException
from dex.utils.ReturnCode import ReturnCode
class DebuggerBase(object, metaclass=abc.ABCMeta):
def __init__(self, context, step_collection):
self.context = context
self.steps = step_collection
self._interface = None
self.has_loaded = False
self._loading_error = NotYetLoadedDebuggerException()
self.watches = set()
try:
self._interface = self._load_interface()
self.has_loaded = True
self._loading_error = None
except DebuggerException:
self._loading_error = sys.exc_info()
self.step_index = 0
def __enter__(self):
try:
self._custom_init()
self.clear_breakpoints()
self.add_breakpoints()
except DebuggerException:
self._loading_error = sys.exc_info()
return self
def __exit__(self, *args):
self._custom_exit()
def _custom_init(self):
pass
def _custom_exit(self):
pass
@property
def debugger_info(self):
return DebuggerIR(name=self.name, version=self.version)
@property
def is_available(self):
return self.has_loaded and self.loading_error is None
@property
def loading_error(self):
return (str(self._loading_error[1])
if self._loading_error is not None else None)
@property
def loading_error_trace(self):
if not self._loading_error:
return None
tb = traceback.format_exception(*self._loading_error)
if self._loading_error[1].orig_exception is not None:
orig_exception = traceback.format_exception(
*self._loading_error[1].orig_exception)
if ''.join(orig_exception) not in ''.join(tb):
tb.extend(['\n'])
tb.extend(orig_exception)
tb = ''.join(tb).splitlines(True)
return tb
def add_breakpoints(self):
for s in self.context.options.source_files:
with open(s, 'r') as fp:
num_lines = len(fp.readlines())
for line in range(1, num_lines + 1):
self.add_breakpoint(s, line)
def _update_step_watches(self, step_info):
loc = step_info.current_location
watch_cmds = ['DexUnreachable', 'DexExpectStepOrder']
towatch = chain.from_iterable(self.steps.commands[x]
for x in watch_cmds
if x in self.steps.commands)
try:
# Iterate over all watches of the types named in watch_cmds
for watch in towatch:
if (os.path.exists(loc.path)
and os.path.samefile(watch.path, loc.path)
and watch.lineno == loc.lineno):
result = watch.eval(self)
step_info.watches.update(result)
break
except KeyError:
pass
def _sanitize_function_name(self, name): # pylint: disable=no-self-use
"""If the function name returned by the debugger needs any post-
processing to make it fit (for example, if it includes a byte offset),
do that here.
"""
return name
def start(self):
self.steps.clear_steps()
self.launch()
for command_obj in chain.from_iterable(self.steps.commands.values()):
self.watches.update(command_obj.get_watches())
max_steps = self.context.options.max_steps
for _ in range(max_steps):
while self.is_running:
pass
if self.is_finished:
break
self.step_index += 1
step_info = self.get_step_info()
if step_info.current_frame:
self._update_step_watches(step_info)
self.steps.new_step(self.context, step_info)
if self.in_source_file(step_info):
self.step()
else:
self.go()
time.sleep(self.context.options.pause_between_steps)
else:
raise DebuggerException(
'maximum number of steps reached ({})'.format(max_steps))
def in_source_file(self, step_info):
if not step_info.current_frame:
return False
if not os.path.exists(step_info.current_location.path):
return False
return any(os.path.samefile(step_info.current_location.path, f) \
for f in self.context.options.source_files)
@abc.abstractmethod
def _load_interface(self):
pass
@classmethod
def get_option_name(cls):
"""Short name that will be used on the command line to specify this
debugger.
"""
raise NotImplementedError()
@classmethod
def get_name(cls):
"""Full name of this debugger."""
raise NotImplementedError()
@property
def name(self):
return self.__class__.get_name()
@property
def option_name(self):
return self.__class__.get_option_name()
@abc.abstractproperty
def version(self):
pass
@abc.abstractmethod
def clear_breakpoints(self):
pass
@abc.abstractmethod
def add_breakpoint(self, file_, line):
pass
@abc.abstractmethod
def launch(self):
pass
@abc.abstractmethod
def step(self):
pass
@abc.abstractmethod
def go(self) -> ReturnCode:
pass
@abc.abstractmethod
def get_step_info(self):
pass
@abc.abstractproperty
def is_running(self):
pass
@abc.abstractproperty
def is_finished(self):
pass
@abc.abstractproperty
def frames_below_main(self):
pass
@abc.abstractmethod
def evaluate_expression(self, expression, frame_idx=0) -> ValueIR:
pass

View File

@ -1,299 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Discover potential/available debugger interfaces."""
from collections import OrderedDict
import os
import pickle
import subprocess
import sys
from tempfile import NamedTemporaryFile
from dex.command import find_all_commands
from dex.dextIR import DextIR
from dex.utils import get_root_directory, Timer
from dex.utils.Environment import is_native_windows
from dex.utils.Exceptions import CommandParseError, DebuggerException
from dex.utils.Exceptions import ToolArgumentError
from dex.utils.Warning import warn
from dex.debugger.dbgeng.dbgeng import DbgEng
from dex.debugger.lldb.LLDB import LLDB
from dex.debugger.visualstudio.VisualStudio2015 import VisualStudio2015
from dex.debugger.visualstudio.VisualStudio2017 import VisualStudio2017
def _get_potential_debuggers(): # noqa
"""Return a dict of the supported debuggers.
Returns:
{ name (str): debugger (class) }
"""
return {
DbgEng.get_option_name(): DbgEng,
LLDB.get_option_name(): LLDB,
VisualStudio2015.get_option_name(): VisualStudio2015,
VisualStudio2017.get_option_name(): VisualStudio2017
}
def _warn_meaningless_option(context, option):
if context.options.list_debuggers:
return
warn(context,
'option <y>"{}"</> is meaningless with this debugger'.format(option),
'--debugger={}'.format(context.options.debugger))
def add_debugger_tool_base_arguments(parser, defaults):
defaults.lldb_executable = 'lldb.exe' if is_native_windows() else 'lldb'
parser.add_argument(
'--lldb-executable',
type=str,
metavar='<file>',
default=None,
display_default=defaults.lldb_executable,
help='location of LLDB executable')
def add_debugger_tool_arguments(parser, context, defaults):
debuggers = Debuggers(context)
potential_debuggers = sorted(debuggers.potential_debuggers().keys())
add_debugger_tool_base_arguments(parser, defaults)
parser.add_argument(
'--debugger',
type=str,
choices=potential_debuggers,
required=True,
help='debugger to use')
parser.add_argument(
'--max-steps',
metavar='<int>',
type=int,
default=1000,
help='maximum number of program steps allowed')
parser.add_argument(
'--pause-between-steps',
metavar='<seconds>',
type=float,
default=0.0,
help='number of seconds to pause between steps')
defaults.show_debugger = False
parser.add_argument(
'--show-debugger',
action='store_true',
default=None,
help='show the debugger')
defaults.arch = 'x86_64'
parser.add_argument(
'--arch',
type=str,
metavar='<architecture>',
default=None,
display_default=defaults.arch,
help='target architecture')
def handle_debugger_tool_base_options(context, defaults): # noqa
options = context.options
if options.lldb_executable is None:
options.lldb_executable = defaults.lldb_executable
else:
if getattr(options, 'debugger', 'lldb') != 'lldb':
_warn_meaningless_option(context, '--lldb-executable')
options.lldb_executable = os.path.abspath(options.lldb_executable)
if not os.path.isfile(options.lldb_executable):
raise ToolArgumentError('<d>could not find</> <r>"{}"</>'.format(
options.lldb_executable))
def handle_debugger_tool_options(context, defaults): # noqa
options = context.options
handle_debugger_tool_base_options(context, defaults)
if options.arch is None:
options.arch = defaults.arch
else:
if options.debugger != 'lldb':
_warn_meaningless_option(context, '--arch')
if options.show_debugger is None:
options.show_debugger = defaults.show_debugger
else:
if options.debugger == 'lldb':
_warn_meaningless_option(context, '--show-debugger')
def _get_command_infos(context):
commands = find_all_commands(context.options.source_files)
command_infos = OrderedDict()
for command_type in commands:
for command in commands[command_type].values():
if command_type not in command_infos:
command_infos[command_type] = []
command_infos[command_type].append(command)
return OrderedDict(command_infos)
def empty_debugger_steps(context):
return DextIR(
executable_path=context.options.executable,
source_paths=context.options.source_files,
dexter_version=context.version)
def get_debugger_steps(context):
step_collection = empty_debugger_steps(context)
with Timer('parsing commands'):
try:
step_collection.commands = _get_command_infos(context)
except CommandParseError as e:
msg = 'parser error: <d>{}({}):</> {}\n{}\n{}\n'.format(
e.filename, e.lineno, e.info, e.src, e.caret)
raise DebuggerException(msg)
with NamedTemporaryFile(
dir=context.working_directory.path, delete=False) as fp:
pickle.dump(step_collection, fp, protocol=pickle.HIGHEST_PROTOCOL)
steps_path = fp.name
with NamedTemporaryFile(
dir=context.working_directory.path, delete=False, mode='wb') as fp:
pickle.dump(context.options, fp, protocol=pickle.HIGHEST_PROTOCOL)
options_path = fp.name
dexter_py = os.path.basename(sys.argv[0])
if not os.path.isfile(dexter_py):
dexter_py = os.path.join(get_root_directory(), '..', dexter_py)
assert os.path.isfile(dexter_py)
with NamedTemporaryFile(dir=context.working_directory.path) as fp:
args = [
sys.executable, dexter_py, 'run-debugger-internal-', steps_path,
options_path, '--working-directory', context.working_directory.path,
'--unittest=off', '--indent-timer-level={}'.format(Timer.indent + 2)
]
try:
with Timer('running external debugger process'):
subprocess.check_call(args)
except subprocess.CalledProcessError as e:
raise DebuggerException(e)
with open(steps_path, 'rb') as fp:
step_collection = pickle.load(fp)
return step_collection
class Debuggers(object):
@classmethod
def potential_debuggers(cls):
try:
return cls._potential_debuggers
except AttributeError:
cls._potential_debuggers = _get_potential_debuggers()
return cls._potential_debuggers
def __init__(self, context):
self.context = context
def load(self, key, step_collection=None):
with Timer('load {}'.format(key)):
return Debuggers.potential_debuggers()[key](self.context,
step_collection)
def _populate_debugger_cache(self):
debuggers = []
for key in sorted(Debuggers.potential_debuggers()):
debugger = self.load(key)
class LoadedDebugger(object):
pass
LoadedDebugger.option_name = key
LoadedDebugger.full_name = '[{}]'.format(debugger.name)
LoadedDebugger.is_available = debugger.is_available
if LoadedDebugger.is_available:
try:
LoadedDebugger.version = debugger.version.splitlines()
except AttributeError:
LoadedDebugger.version = ['']
else:
try:
LoadedDebugger.error = debugger.loading_error.splitlines()
except AttributeError:
LoadedDebugger.error = ['']
try:
LoadedDebugger.error_trace = debugger.loading_error_trace
except AttributeError:
LoadedDebugger.error_trace = None
debuggers.append(LoadedDebugger)
return debuggers
def list(self):
debuggers = self._populate_debugger_cache()
max_o_len = max(len(d.option_name) for d in debuggers)
max_n_len = max(len(d.full_name) for d in debuggers)
msgs = []
for d in debuggers:
# Option name, right padded with spaces for alignment
option_name = (
'{{name: <{}}}'.format(max_o_len).format(name=d.option_name))
# Full name, right padded with spaces for alignment
full_name = ('{{name: <{}}}'.format(max_n_len)
.format(name=d.full_name))
if d.is_available:
name = '<b>{} {}</>'.format(option_name, full_name)
# If the debugger is available, show the first line of the
# version info.
available = '<g>YES</>'
info = '<b>({})</>'.format(d.version[0])
else:
name = '<y>{} {}</>'.format(option_name, full_name)
# If the debugger is not available, show the first line of the
# error reason.
available = '<r>NO</> '
info = '<y>({})</>'.format(d.error[0])
msg = '{} {} {}'.format(name, available, info)
if self.context.options.verbose:
# If verbose mode and there was more version or error output
# than could be displayed in a single line, display the whole
# lot slightly indented.
verbose_info = None
if d.is_available:
if d.version[1:]:
verbose_info = d.version + ['\n']
else:
# Some of list elems may contain multiple lines, so make
# sure each elem is a line of its own.
verbose_info = d.error_trace
if verbose_info:
verbose_info = '\n'.join(' {}'.format(l.rstrip())
for l in verbose_info) + '\n'
msg = '{}\n\n{}'.format(msg, verbose_info)
msgs.append(msg)
self.context.o.auto('\n{}\n\n'.format('\n'.join(msgs)))

View File

@ -1,8 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.debugger.Debuggers import Debuggers

View File

@ -1,60 +0,0 @@
# Debugger Engine backend
This directory contains the Dexter backend for the Windows Debugger Engine
(DbgEng), which powers tools such as WinDbg and CDB.
## Overview
DbgEng is available as a collection of unregistered COM-"like" objects that
one accesses by calling DebugCreate in DbgEng.dll. The unregistered nature
means normal COM tooling can't access them; as a result, this backend uses
ctypes to describe the COM objects and call their methods.
This is obviously not a huge amount of fun; on the other hand, COM has
maintained ABI compatible interfaces for decades, and nothing is for free.
The dexter backend follows the same formula as others; it creates a process
and breaks on "main", then steps through the program, observing states and
stack frames along the way.
## Implementation details
This backend uses a mixture of both APIs for accessing information, and the
direct command-string interface to DbgEng for performing some actions. We
have to use the DbgEng stepping interface, or we would effectively be
building a new debugger, but certain things (like enabling source-line
stepping) only seem to be possible from the command interface.
Each segment of debugger responsibility has its own COM object: Client,
Control, Symbols, SymbolGroups, Breakpoint, SystemObjects. In this python
wrapper, each COM object gets a python object wrapping it. COM methods
that are relevant to our interests have a python method that wraps the COM
one and performs data marshalling. Some additional helper methods are added
to the python objects to extract data.
The majority of the work occurs in setup.py and probe_process.py. The
former contains routines to launch a process and attach the debugger to
it, while the latter extracts as much information as possible from a
stopped process, returning a list of stack frames with associated variable
information.
## Sharp edges
For reasons still unclear, using CreateProcessAndAttach never appears to
allow the debuggee to resume, hence this implementation creates the
debuggee process manually, attaches, and resumes.
On process startup, we set a breakpoint on main and then continue running
to it. This has the potential to never complete -- although of course,
there's no guarantee that the debuggee will ever do anything anyway.
There doesn't appear to be a way to instruct DbgEng to "step into" a
function call, thus after reaching main, we scan the module for all
functions with line numbers in the source directory, and put breakpoints
on them. An alternative implementation would be putting breakpoints on
every known line number.
Finally, it's unclear whether arbitrary expressions can be evaluated in
arbitrary stack frames, although this isn't something that Dexter currently
supports.

View File

@ -1,19 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from . import dbgeng
import platform
if platform.system() == 'Windows':
from . import breakpoint
from . import control
from . import probe_process
from . import setup
from . import symbols
from . import symgroup
from . import sysobjs
from . import utils

View File

@ -1,88 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from ctypes import *
from enum import *
from functools import partial
from .utils import *
class BreakpointTypes(IntEnum):
DEBUG_BREAKPOINT_CODE = 0
DEBUG_BREAKPOINT_DATA = 1
DEBUG_BREAKPOINT_TIME = 2
DEBUG_BREAKPOINT_INLINE = 3
class BreakpointFlags(IntFlag):
DEBUG_BREAKPOINT_GO_ONLY = 0x00000001
DEBUG_BREAKPOINT_DEFERRED = 0x00000002
DEBUG_BREAKPOINT_ENABLED = 0x00000004
DEBUG_BREAKPOINT_ADDER_ONLY = 0x00000008
DEBUG_BREAKPOINT_ONE_SHOT = 0x00000010
DebugBreakpoint2IID = IID(0x1b278d20, 0x79f2, 0x426e, IID_Data4_Type(0xa3, 0xf9, 0xc1, 0xdd, 0xf3, 0x75, 0xd4, 0x8e))
class DebugBreakpoint2(Structure):
pass
class DebugBreakpoint2Vtbl(Structure):
wrp = partial(WINFUNCTYPE, c_long, POINTER(DebugBreakpoint2))
idb_setoffset = wrp(c_ulonglong)
idb_setflags = wrp(c_ulong)
_fields_ = [
("QueryInterface", c_void_p),
("AddRef", c_void_p),
("Release", c_void_p),
("GetId", c_void_p),
("GetType", c_void_p),
("GetAdder", c_void_p),
("GetFlags", c_void_p),
("AddFlags", c_void_p),
("RemoveFlags", c_void_p),
("SetFlags", idb_setflags),
("GetOffset", c_void_p),
("SetOffset", idb_setoffset),
("GetDataParameters", c_void_p),
("SetDataParameters", c_void_p),
("GetPassCount", c_void_p),
("SetPassCount", c_void_p),
("GetCurrentPassCount", c_void_p),
("GetMatchThreadId", c_void_p),
("SetMatchThreadId", c_void_p),
("GetCommand", c_void_p),
("SetCommand", c_void_p),
("GetOffsetExpression", c_void_p),
("SetOffsetExpression", c_void_p),
("GetParameters", c_void_p),
("GetCommandWide", c_void_p),
("SetCommandWide", c_void_p),
("GetOffsetExpressionWide", c_void_p),
("SetOffsetExpressionWide", c_void_p)
]
DebugBreakpoint2._fields_ = [("lpVtbl", POINTER(DebugBreakpoint2Vtbl))]
class Breakpoint(object):
def __init__(self, breakpoint):
self.breakpoint = breakpoint.contents
self.vt = self.breakpoint.lpVtbl.contents
def SetFlags(self, flags):
res = self.vt.SetFlags(self.breakpoint, flags)
aborter(res, "Breakpoint SetFlags")
def SetOffset(self, offs):
res = self.vt.SetOffset(self.breakpoint, offs)
aborter(res, "Breakpoint SetOffset")
def RemoveFlags(self, flags):
res = self.vt.RemoveFlags(self.breakpoint, flags)
aborter(res, "Breakpoint RemoveFlags")
def die(self):
self.breakpoint = None
self.vt = None

View File

@ -1,185 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from ctypes import *
from enum import *
from functools import partial
from .utils import *
from . import control
from . import symbols
from . import sysobjs
class DebugAttach(IntFlag):
DEBUG_ATTACH_DEFAULT = 0
DEBUG_ATTACH_NONINVASIVE = 1
DEBUG_ATTACH_EXISTING = 2
DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND = 4
DEBUG_ATTACH_INVASIVE_NO_INITIAL_BREAK = 8
DEBUG_ATTACH_INVASIVE_RESUME_PROCESS = 0x10
DEBUG_ATTACH_NONINVASIVE_ALLOW_PARTIAL = 0x20
# UUID for DebugClient7 interface.
DebugClient7IID = IID(0x13586be3, 0x542e, 0x481e, IID_Data4_Type(0xb1, 0xf2, 0x84, 0x97, 0xba, 0x74, 0xf9, 0xa9 ))
class IDebugClient7(Structure):
pass
class IDebugClient7Vtbl(Structure):
wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugClient7))
idc_queryinterface = wrp(POINTER(IID), POINTER(c_void_p))
idc_attachprocess = wrp(c_longlong, c_long, c_long)
idc_detachprocesses = wrp()
_fields_ = [
("QueryInterface", idc_queryinterface),
("AddRef", c_void_p),
("Release", c_void_p),
("AttachKernel", c_void_p),
("GetKernelConnectionOptions", c_void_p),
("SetKernelConnectionOptions", c_void_p),
("StartProcessServer", c_void_p),
("ConnectProcessServer", c_void_p),
("DisconnectProcessServer", c_void_p),
("GetRunningProcessSystemIds", c_void_p),
("GetRunningProcessSystemIdsByExecutableName", c_void_p),
("GetRunningProcessDescription", c_void_p),
("AttachProcess", idc_attachprocess),
("CreateProcess", c_void_p),
("CreateProcessAndAttach", c_void_p),
("GetProcessOptions", c_void_p),
("AddProcessOptions", c_void_p),
("RemoveProcessOptions", c_void_p),
("SetProcessOptions", c_void_p),
("OpenDumpFile", c_void_p),
("WriteDumpFile", c_void_p),
("ConnectSession", c_void_p),
("StartServer", c_void_p),
("OutputServers", c_void_p),
("TerminateProcesses", c_void_p),
("DetachProcesses", idc_detachprocesses),
("EndSession", c_void_p),
("GetExitCode", c_void_p),
("DispatchCallbacks", c_void_p),
("ExitDispatch", c_void_p),
("CreateClient", c_void_p),
("GetInputCallbacks", c_void_p),
("SetInputCallbacks", c_void_p),
("GetOutputCallbacks", c_void_p),
("SetOutputCallbacks", c_void_p),
("GetOutputMask", c_void_p),
("SetOutputMask", c_void_p),
("GetOtherOutputMask", c_void_p),
("SetOtherOutputMask", c_void_p),
("GetOutputWidth", c_void_p),
("SetOutputWidth", c_void_p),
("GetOutputLinePrefix", c_void_p),
("SetOutputLinePrefix", c_void_p),
("GetIdentity", c_void_p),
("OutputIdentity", c_void_p),
("GetEventCallbacks", c_void_p),
("SetEventCallbacks", c_void_p),
("FlushCallbacks", c_void_p),
("WriteDumpFile2", c_void_p),
("AddDumpInformationFile", c_void_p),
("EndProcessServer", c_void_p),
("WaitForProcessServerEnd", c_void_p),
("IsKernelDebuggerEnabled", c_void_p),
("TerminateCurrentProcess", c_void_p),
("DetachCurrentProcess", c_void_p),
("AbandonCurrentProcess", c_void_p),
("GetRunningProcessSystemIdByExecutableNameWide", c_void_p),
("GetRunningProcessDescriptionWide", c_void_p),
("CreateProcessWide", c_void_p),
("CreateProcessAndAttachWide", c_void_p),
("OpenDumpFileWide", c_void_p),
("WriteDumpFileWide", c_void_p),
("AddDumpInformationFileWide", c_void_p),
("GetNumberDumpFiles", c_void_p),
("GetDumpFile", c_void_p),
("GetDumpFileWide", c_void_p),
("AttachKernelWide", c_void_p),
("GetKernelConnectionOptionsWide", c_void_p),
("SetKernelConnectionOptionsWide", c_void_p),
("StartProcessServerWide", c_void_p),
("ConnectProcessServerWide", c_void_p),
("StartServerWide", c_void_p),
("OutputServerWide", c_void_p),
("GetOutputCallbacksWide", c_void_p),
("SetOutputCallbacksWide", c_void_p),
("GetOutputLinePrefixWide", c_void_p),
("SetOutputLinePrefixWide", c_void_p),
("GetIdentityWide", c_void_p),
("OutputIdentityWide", c_void_p),
("GetEventCallbacksWide", c_void_p),
("SetEventCallbacksWide", c_void_p),
("CreateProcess2", c_void_p),
("CreateProcess2Wide", c_void_p),
("CreateProcessAndAttach2", c_void_p),
("CreateProcessAndAttach2Wide", c_void_p),
("PushOutputLinePrefix", c_void_p),
("PushOutputLinePrefixWide", c_void_p),
("PopOutputLinePrefix", c_void_p),
("GetNumberInputCallbacks", c_void_p),
("GetNumberOutputCallbacks", c_void_p),
("GetNumberEventCallbacks", c_void_p),
("GetQuitLockString", c_void_p),
("SetQuitLockString", c_void_p),
("GetQuitLockStringWide", c_void_p),
("SetQuitLockStringWide", c_void_p),
("SetEventContextCallbacks", c_void_p),
("SetClientContext", c_void_p),
]
IDebugClient7._fields_ = [("lpVtbl", POINTER(IDebugClient7Vtbl))]
class Client(object):
def __init__(self):
DbgEng = WinDLL("DbgEng")
DbgEng.DebugCreate.argtypes = [POINTER(IID), POINTER(POINTER(IDebugClient7))]
DbgEng.DebugCreate.restype = c_ulong
# Call DebugCreate to create a new debug client
ptr = POINTER(IDebugClient7)()
res = DbgEng.DebugCreate(byref(DebugClient7IID), ptr)
aborter(res, "DebugCreate")
self.client = ptr.contents
self.vt = vt = self.client.lpVtbl.contents
def QI(iface, ptr):
return vt.QueryInterface(self.client, byref(iface), byref(ptr))
# Query for a control object
ptr = c_void_p()
res = QI(control.DebugControl7IID, ptr)
aborter(res, "QueryInterface control")
self.control_ptr = cast(ptr, POINTER(control.IDebugControl7))
self.Control = control.Control(self.control_ptr)
# Query for a SystemObjects object
ptr = c_void_p()
res = QI(sysobjs.DebugSystemObjects4IID, ptr)
aborter(res, "QueryInterface sysobjects")
self.sysobjects_ptr = cast(ptr, POINTER(sysobjs.IDebugSystemObjects4))
self.SysObjects = sysobjs.SysObjects(self.sysobjects_ptr)
# Query for a Symbols object
ptr = c_void_p()
res = QI(symbols.DebugSymbols5IID, ptr)
aborter(res, "QueryInterface debugsymbosl5")
self.symbols_ptr = cast(ptr, POINTER(symbols.IDebugSymbols5))
self.Symbols = symbols.Symbols(self.symbols_ptr)
def AttachProcess(self, pid):
# Zero process-server id means no process-server.
res = self.vt.AttachProcess(self.client, 0, pid, DebugAttach.DEBUG_ATTACH_DEFAULT)
aborter(res, "AttachProcess")
return
def DetachProcesses(self):
res = self.vt.DetachProcesses(self.client)
aborter(res, "DetachProcesses")
return

View File

@ -1,405 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from ctypes import *
from functools import partial
from .utils import *
from .breakpoint import *
class DEBUG_STACK_FRAME_EX(Structure):
_fields_ = [
("InstructionOffset", c_ulonglong),
("ReturnOffset", c_ulonglong),
("FrameOffset", c_ulonglong),
("StackOffset", c_ulonglong),
("FuncTableEntry", c_ulonglong),
("Params", c_ulonglong * 4),
("Reserved", c_ulonglong * 6),
("Virtual", c_bool),
("FrameNumber", c_ulong),
("InlineFrameContext", c_ulong),
("Reserved1", c_ulong)
]
PDEBUG_STACK_FRAME_EX = POINTER(DEBUG_STACK_FRAME_EX)
class DEBUG_VALUE_U(Union):
_fields_ = [
("I8", c_byte),
("I16", c_short),
("I32", c_int),
("I64", c_long),
("F32", c_float),
("F64", c_double),
("RawBytes", c_ubyte * 24) # Force length to 24b.
]
class DEBUG_VALUE(Structure):
_fields_ = [
("U", DEBUG_VALUE_U),
("TailOfRawBytes", c_ulong),
("Type", c_ulong)
]
PDEBUG_VALUE = POINTER(DEBUG_VALUE)
class DebugValueType(IntEnum):
DEBUG_VALUE_INVALID = 0
DEBUG_VALUE_INT8 = 1
DEBUG_VALUE_INT16 = 2
DEBUG_VALUE_INT32 = 3
DEBUG_VALUE_INT64 = 4
DEBUG_VALUE_FLOAT32 = 5
DEBUG_VALUE_FLOAT64 = 6
DEBUG_VALUE_FLOAT80 = 7
DEBUG_VALUE_FLOAT82 = 8
DEBUG_VALUE_FLOAT128 = 9
DEBUG_VALUE_VECTOR64 = 10
DEBUG_VALUE_VECTOR128 = 11
DEBUG_VALUE_TYPES = 12
# UUID for DebugControl7 interface.
DebugControl7IID = IID(0xb86fb3b1, 0x80d4, 0x475b, IID_Data4_Type(0xae, 0xa3, 0xcf, 0x06, 0x53, 0x9c, 0xf6, 0x3a))
class IDebugControl7(Structure):
pass
class IDebugControl7Vtbl(Structure):
wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugControl7))
idc_getnumbereventfilters = wrp(c_ulong_p, c_ulong_p, c_ulong_p)
idc_setexceptionfiltersecondcommand = wrp(c_ulong, c_char_p)
idc_waitforevent = wrp(c_long, c_long)
idc_execute = wrp(c_long, c_char_p, c_long)
idc_setexpressionsyntax = wrp(c_ulong)
idc_addbreakpoint2 = wrp(c_ulong, c_ulong, POINTER(POINTER(DebugBreakpoint2)))
idc_setexecutionstatus = wrp(c_ulong)
idc_getexecutionstatus = wrp(c_ulong_p)
idc_getstacktraceex = wrp(c_ulonglong, c_ulonglong, c_ulonglong, PDEBUG_STACK_FRAME_EX, c_ulong, c_ulong_p)
idc_evaluate = wrp(c_char_p, c_ulong, PDEBUG_VALUE, c_ulong_p)
_fields_ = [
("QueryInterface", c_void_p),
("AddRef", c_void_p),
("Release", c_void_p),
("GetInterrupt", c_void_p),
("SetInterrupt", c_void_p),
("GetInterruptTimeout", c_void_p),
("SetInterruptTimeout", c_void_p),
("GetLogFile", c_void_p),
("OpenLogFile", c_void_p),
("CloseLogFile", c_void_p),
("GetLogMask", c_void_p),
("SetLogMask", c_void_p),
("Input", c_void_p),
("ReturnInput", c_void_p),
("Output", c_void_p),
("OutputVaList", c_void_p),
("ControlledOutput", c_void_p),
("ControlledOutputVaList", c_void_p),
("OutputPrompt", c_void_p),
("OutputPromptVaList", c_void_p),
("GetPromptText", c_void_p),
("OutputCurrentState", c_void_p),
("OutputVersionInformation", c_void_p),
("GetNotifyEventHandle", c_void_p),
("SetNotifyEventHandle", c_void_p),
("Assemble", c_void_p),
("Disassemble", c_void_p),
("GetDisassembleEffectiveOffset", c_void_p),
("OutputDisassembly", c_void_p),
("OutputDisassemblyLines", c_void_p),
("GetNearInstruction", c_void_p),
("GetStackTrace", c_void_p),
("GetReturnOffset", c_void_p),
("OutputStackTrace", c_void_p),
("GetDebuggeeType", c_void_p),
("GetActualProcessorType", c_void_p),
("GetExecutingProcessorType", c_void_p),
("GetNumberPossibleExecutingProcessorTypes", c_void_p),
("GetPossibleExecutingProcessorTypes", c_void_p),
("GetNumberProcessors", c_void_p),
("GetSystemVersion", c_void_p),
("GetPageSize", c_void_p),
("IsPointer64Bit", c_void_p),
("ReadBugCheckData", c_void_p),
("GetNumberSupportedProcessorTypes", c_void_p),
("GetSupportedProcessorTypes", c_void_p),
("GetProcessorTypeNames", c_void_p),
("GetEffectiveProcessorType", c_void_p),
("SetEffectiveProcessorType", c_void_p),
("GetExecutionStatus", idc_getexecutionstatus),
("SetExecutionStatus", idc_setexecutionstatus),
("GetCodeLevel", c_void_p),
("SetCodeLevel", c_void_p),
("GetEngineOptions", c_void_p),
("AddEngineOptions", c_void_p),
("RemoveEngineOptions", c_void_p),
("SetEngineOptions", c_void_p),
("GetSystemErrorControl", c_void_p),
("SetSystemErrorControl", c_void_p),
("GetTextMacro", c_void_p),
("SetTextMacro", c_void_p),
("GetRadix", c_void_p),
("SetRadix", c_void_p),
("Evaluate", idc_evaluate),
("CoerceValue", c_void_p),
("CoerceValues", c_void_p),
("Execute", idc_execute),
("ExecuteCommandFile", c_void_p),
("GetNumberBreakpoints", c_void_p),
("GetBreakpointByIndex", c_void_p),
("GetBreakpointById", c_void_p),
("GetBreakpointParameters", c_void_p),
("AddBreakpoint", c_void_p),
("RemoveBreakpoint", c_void_p),
("AddExtension", c_void_p),
("RemoveExtension", c_void_p),
("GetExtensionByPath", c_void_p),
("CallExtension", c_void_p),
("GetExtensionFunction", c_void_p),
("GetWindbgExtensionApis32", c_void_p),
("GetWindbgExtensionApis64", c_void_p),
("GetNumberEventFilters", idc_getnumbereventfilters),
("GetEventFilterText", c_void_p),
("GetEventFilterCommand", c_void_p),
("SetEventFilterCommand", c_void_p),
("GetSpecificFilterParameters", c_void_p),
("SetSpecificFilterParameters", c_void_p),
("GetSpecificFilterArgument", c_void_p),
("SetSpecificFilterArgument", c_void_p),
("GetExceptionFilterParameters", c_void_p),
("SetExceptionFilterParameters", c_void_p),
("GetExceptionFilterSecondCommand", c_void_p),
("SetExceptionFilterSecondCommand", idc_setexceptionfiltersecondcommand),
("WaitForEvent", idc_waitforevent),
("GetLastEventInformation", c_void_p),
("GetCurrentTimeDate", c_void_p),
("GetCurrentSystemUpTime", c_void_p),
("GetDumpFormatFlags", c_void_p),
("GetNumberTextReplacements", c_void_p),
("GetTextReplacement", c_void_p),
("SetTextReplacement", c_void_p),
("RemoveTextReplacements", c_void_p),
("OutputTextReplacements", c_void_p),
("GetAssemblyOptions", c_void_p),
("AddAssemblyOptions", c_void_p),
("RemoveAssemblyOptions", c_void_p),
("SetAssemblyOptions", c_void_p),
("GetExpressionSyntax", c_void_p),
("SetExpressionSyntax", idc_setexpressionsyntax),
("SetExpressionSyntaxByName", c_void_p),
("GetNumberExpressionSyntaxes", c_void_p),
("GetExpressionSyntaxNames", c_void_p),
("GetNumberEvents", c_void_p),
("GetEventIndexDescription", c_void_p),
("GetCurrentEventIndex", c_void_p),
("SetNextEventIndex", c_void_p),
("GetLogFileWide", c_void_p),
("OpenLogFileWide", c_void_p),
("InputWide", c_void_p),
("ReturnInputWide", c_void_p),
("OutputWide", c_void_p),
("OutputVaListWide", c_void_p),
("ControlledOutputWide", c_void_p),
("ControlledOutputVaListWide", c_void_p),
("OutputPromptWide", c_void_p),
("OutputPromptVaListWide", c_void_p),
("GetPromptTextWide", c_void_p),
("AssembleWide", c_void_p),
("DisassembleWide", c_void_p),
("GetProcessrTypeNamesWide", c_void_p),
("GetTextMacroWide", c_void_p),
("SetTextMacroWide", c_void_p),
("EvaluateWide", c_void_p),
("ExecuteWide", c_void_p),
("ExecuteCommandFileWide", c_void_p),
("GetBreakpointByIndex2", c_void_p),
("GetBreakpointById2", c_void_p),
("AddBreakpoint2", idc_addbreakpoint2),
("RemoveBreakpoint2", c_void_p),
("AddExtensionWide", c_void_p),
("GetExtensionByPathWide", c_void_p),
("CallExtensionWide", c_void_p),
("GetExtensionFunctionWide", c_void_p),
("GetEventFilterTextWide", c_void_p),
("GetEventfilterCommandWide", c_void_p),
("SetEventFilterCommandWide", c_void_p),
("GetSpecificFilterArgumentWide", c_void_p),
("SetSpecificFilterArgumentWide", c_void_p),
("GetExceptionFilterSecondCommandWide", c_void_p),
("SetExceptionFilterSecondCommandWider", c_void_p),
("GetLastEventInformationWide", c_void_p),
("GetTextReplacementWide", c_void_p),
("SetTextReplacementWide", c_void_p),
("SetExpressionSyntaxByNameWide", c_void_p),
("GetExpressionSyntaxNamesWide", c_void_p),
("GetEventIndexDescriptionWide", c_void_p),
("GetLogFile2", c_void_p),
("OpenLogFile2", c_void_p),
("GetLogFile2Wide", c_void_p),
("OpenLogFile2Wide", c_void_p),
("GetSystemVersionValues", c_void_p),
("GetSystemVersionString", c_void_p),
("GetSystemVersionStringWide", c_void_p),
("GetContextStackTrace", c_void_p),
("OutputContextStackTrace", c_void_p),
("GetStoredEventInformation", c_void_p),
("GetManagedStatus", c_void_p),
("GetManagedStatusWide", c_void_p),
("ResetManagedStatus", c_void_p),
("GetStackTraceEx", idc_getstacktraceex),
("OutputStackTraceEx", c_void_p),
("GetContextStackTraceEx", c_void_p),
("OutputContextStackTraceEx", c_void_p),
("GetBreakpointByGuid", c_void_p),
("GetExecutionStatusEx", c_void_p),
("GetSynchronizationStatus", c_void_p),
("GetDebuggeeType2", c_void_p)
]
IDebugControl7._fields_ = [("lpVtbl", POINTER(IDebugControl7Vtbl))]
class DebugStatus(IntEnum):
DEBUG_STATUS_NO_CHANGE = 0
DEBUG_STATUS_GO = 1
DEBUG_STATUS_GO_HANDLED = 2
DEBUG_STATUS_GO_NOT_HANDLED = 3
DEBUG_STATUS_STEP_OVER = 4
DEBUG_STATUS_STEP_INTO = 5
DEBUG_STATUS_BREAK = 6
DEBUG_STATUS_NO_DEBUGGEE = 7
DEBUG_STATUS_STEP_BRANCH = 8
DEBUG_STATUS_IGNORE_EVENT = 9
DEBUG_STATUS_RESTART_REQUESTED = 10
DEBUG_STATUS_REVERSE_GO = 11
DEBUG_STATUS_REVERSE_STEP_BRANCH = 12
DEBUG_STATUS_REVERSE_STEP_OVER = 13
DEBUG_STATUS_REVERSE_STEP_INTO = 14
DEBUG_STATUS_OUT_OF_SYNC = 15
DEBUG_STATUS_WAIT_INPUT = 16
DEBUG_STATUS_TIMEOUT = 17
class DebugSyntax(IntEnum):
DEBUG_EXPR_MASM = 0
DEBUG_EXPR_CPLUSPLUS = 1
class Control(object):
def __init__(self, control):
self.ptr = control
self.control = control.contents
self.vt = self.control.lpVtbl.contents
# Keep a handy ulong for passing into C methods.
self.ulong = c_ulong()
def GetExecutionStatus(self, doprint=False):
ret = self.vt.GetExecutionStatus(self.control, byref(self.ulong))
aborter(ret, "GetExecutionStatus")
status = DebugStatus(self.ulong.value)
if doprint:
print("Execution status: {}".format(status))
return status
def SetExecutionStatus(self, status):
assert isinstance(status, DebugStatus)
res = self.vt.SetExecutionStatus(self.control, status.value)
aborter(res, "SetExecutionStatus")
def WaitForEvent(self, timeout=100):
# No flags are taken by WaitForEvent, hence 0
ret = self.vt.WaitForEvent(self.control, 0, timeout)
aborter(ret, "WaitforEvent", ignore=[S_FALSE])
return ret
def GetNumberEventFilters(self):
specific_events = c_ulong()
specific_exceptions = c_ulong()
arbitrary_exceptions = c_ulong()
res = self.vt.GetNumberEventFilters(self.control, byref(specific_events),
byref(specific_exceptions),
byref(arbitrary_exceptions))
aborter(res, "GetNumberEventFilters")
return (specific_events.value, specific_exceptions.value,
arbitrary_exceptions.value)
def SetExceptionFilterSecondCommand(self, index, command):
buf = create_string_buffer(command.encode('ascii'))
res = self.vt.SetExceptionFilterSecondCommand(self.control, index, buf)
aborter(res, "SetExceptionFilterSecondCommand")
return
def AddBreakpoint2(self, offset=None, enabled=None):
breakpoint = POINTER(DebugBreakpoint2)()
res = self.vt.AddBreakpoint2(self.control, BreakpointTypes.DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, byref(breakpoint))
aborter(res, "Add breakpoint 2")
bp = Breakpoint(breakpoint)
if offset is not None:
bp.SetOffset(offset)
if enabled is not None and enabled:
bp.SetFlags(BreakpointFlags.DEBUG_BREAKPOINT_ENABLED)
return bp
def RemoveBreakpoint(self, bp):
res = self.vt.RemoveBreakpoint2(self.control, bp.breakpoint)
aborter(res, "RemoveBreakpoint2")
bp.die()
def GetStackTraceEx(self):
# XXX -- I can't find a way to query for how many stack frames there _are_
# in advance. Guess 128 for now.
num_frames_buffer = 128
frames = (DEBUG_STACK_FRAME_EX * num_frames_buffer)()
numframes = c_ulong()
# First three args are frame/stack/IP offsets -- leave them as zero to
# default to the current instruction.
res = self.vt.GetStackTraceEx(self.control, 0, 0, 0, frames, num_frames_buffer, byref(numframes))
aborter(res, "GetStackTraceEx")
return frames, numframes.value
def Execute(self, command):
# First zero is DEBUG_OUTCTL_*, which we leave as a default, second
# zero is DEBUG_EXECUTE_* flags, of which we set none.
res = self.vt.Execute(self.control, 0, command.encode('ascii'), 0)
aborter(res, "Client execute")
def SetExpressionSyntax(self, cpp=True):
if cpp:
syntax = DebugSyntax.DEBUG_EXPR_CPLUSPLUS
else:
syntax = DebugSyntax.DEBUG_EXPR_MASM
res = self.vt.SetExpressionSyntax(self.control, syntax)
aborter(res, "SetExpressionSyntax")
def Evaluate(self, expr):
ptr = DEBUG_VALUE()
res = self.vt.Evaluate(self.control, expr.encode("ascii"), DebugValueType.DEBUG_VALUE_INVALID, byref(ptr), None)
aborter(res, "Evaluate", ignore=[E_INTERNALEXCEPTION, E_FAIL])
if res != 0:
return None
val_type = DebugValueType(ptr.Type)
# Here's a map from debug value types to fields. Unclear what happens
# with unsigned values, as DbgEng doesn't present any unsigned fields.
extract_map = {
DebugValueType.DEBUG_VALUE_INT8 : ("I8", "char"),
DebugValueType.DEBUG_VALUE_INT16 : ("I16", "short"),
DebugValueType.DEBUG_VALUE_INT32 : ("I32", "int"),
DebugValueType.DEBUG_VALUE_INT64 : ("I64", "long"),
DebugValueType.DEBUG_VALUE_FLOAT32 : ("F32", "float"),
DebugValueType.DEBUG_VALUE_FLOAT64 : ("F64", "double")
} # And everything else is invalid.
if val_type not in extract_map:
raise Exception("Unexpected debug value type {} when evalutaing".format(val_type))
# Also produce a type name...
return getattr(ptr.U, extract_map[val_type][0]), extract_map[val_type][1]

View File

@ -1,163 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
import sys
import os
import platform
from dex.debugger.DebuggerBase import DebuggerBase
from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR
from dex.dextIR import ProgramState, StackFrame, SourceLocation
from dex.utils.Exceptions import DebuggerException, LoadDebuggerException
from dex.utils.ReturnCode import ReturnCode
if platform.system() == "Windows":
# Don't load on linux; _load_interface will croak before any names are used.
from . import setup
from . import probe_process
from . import breakpoint
class DbgEng(DebuggerBase):
def __init__(self, context, *args):
self.breakpoints = []
self.running = False
self.finished = False
self.step_info = None
super(DbgEng, self).__init__(context, *args)
def _custom_init(self):
try:
res = setup.setup_everything(self.context.options.executable)
self.client, self.hProcess = res
self.running = True
except Exception as e:
raise Exception('Failed to start debuggee: {}'.format(e))
def _custom_exit(self):
setup.cleanup(self.client, self.hProcess)
def _load_interface(self):
arch = platform.architecture()[0]
machine = platform.machine()
if arch == '32bit' and machine == 'AMD64':
# This python process is 32 bits, but is sitting on a 64 bit machine.
# Bad things may happen, don't support it.
raise LoadDebuggerException('Can\'t run Dexter dbgeng on 32 bit python in a 64 bit environment')
if platform.system() != 'Windows':
raise LoadDebuggerException('DbgEng supports Windows only')
# Otherwise, everything was imported earlier
@classmethod
def get_name(cls):
return 'dbgeng'
@classmethod
def get_option_name(cls):
return 'dbgeng'
@property
def frames_below_main(self):
return []
@property
def version(self):
# I don't believe there's a well defined DbgEng version, outside of the
# version of Windows being used.
return "1"
def clear_breakpoints(self):
for x in self.breakpoints:
x.RemoveFlags(breakpoint.BreakpointFlags.DEBUG_BREAKPOINT_ENABLED)
self.client.Control.RemoveBreakpoint(x)
def add_breakpoint(self, file_, line):
# This is something to implement in the future -- as it stands, Dexter
# doesn't test for such things as "I can set a breakpoint on this line".
# This is only called AFAICT right now to ensure we break on every step.
pass
def launch(self):
# We are, by this point, already launched.
self.step_info = probe_process.probe_state(self.client)
def step(self):
res = setup.step_once(self.client)
if not res:
self.finished = True
self.step_info = res
def go(self):
# We never go -- we always single step.
pass
def get_step_info(self):
frames = self.step_info
state_frames = []
# For now assume the base function is the... function, ignoring
# inlining.
dex_frames = []
for i, x in enumerate(frames):
# XXX Might be able to get columns out through
# GetSourceEntriesByOffset, not a priority now
loc = LocIR(path=x.source_file, lineno=x.line_no, column=0)
new_frame = FrameIR(function=x.function_name, is_inlined=False, loc=loc)
dex_frames.append(new_frame)
state_frame = StackFrame(function=new_frame.function,
is_inlined=new_frame.is_inlined,
location=SourceLocation(path=x.source_file,
lineno=x.line_no,
column=0),
watches={})
for expr in map(
lambda watch, idx=i: self.evaluate_expression(watch, idx),
self.watches):
state_frame.watches[expr.expression] = expr
state_frames.append(state_frame)
return StepIR(
step_index=self.step_index, frames=dex_frames,
stop_reason=StopReason.STEP,
program_state=ProgramState(state_frames))
@property
def is_running(self):
return False # We're never free-running
@property
def is_finished(self):
return self.finished
def evaluate_expression(self, expression, frame_idx=0):
# XXX: cdb insists on using '->' to examine fields of structures,
# as it appears to reserve '.' for other purposes.
fixed_expr = expression.replace('.', '->')
orig_scope_idx = self.client.Symbols.GetCurrentScopeFrameIndex()
self.client.Symbols.SetScopeFrameByIndex(frame_idx)
res = self.client.Control.Evaluate(fixed_expr)
if res is not None:
result, typename = self.client.Control.Evaluate(fixed_expr)
could_eval = True
else:
result, typename = (None, None)
could_eval = False
self.client.Symbols.SetScopeFrameByIndex(orig_scope_idx)
return ValueIR(
expression=expression,
value=str(result),
type_name=typename,
error_string="",
could_evaluate=could_eval,
is_optimized_away=False,
is_irretrievable=not could_eval)

View File

@ -1,80 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
import os
from .utils import *
class Frame(object):
def __init__(self, frame, idx, Symbols):
# Store some base information about the frame
self.ip = frame.InstructionOffset
self.scope_idx = idx
self.virtual = frame.Virtual
self.inline_frame_context = frame.InlineFrameContext
self.func_tbl_entry = frame.FuncTableEntry
# Fetch the module/symbol we're in, with displacement. Useful for debugging.
self.descr = Symbols.GetNearNameByOffset(self.ip)
split = self.descr.split('!')[0]
self.module = split[0]
self.symbol = split[1]
# Fetch symbol group for this scope.
prevscope = Symbols.GetCurrentScopeFrameIndex()
if Symbols.SetScopeFrameByIndex(idx):
symgroup = Symbols.GetScopeSymbolGroup2()
Symbols.SetScopeFrameByIndex(prevscope)
self.symgroup = symgroup
else:
self.symgroup = None
# Fetch the name according to the line-table, using inlining context.
name = Symbols.GetNameByInlineContext(self.ip, self.inline_frame_context)
self.function_name = name.split('!')[-1]
try:
tup = Symbols.GetLineByInlineContext(self.ip, self.inline_frame_context)
self.source_file, self.line_no = tup
except WinError as e:
# Fall back to trying to use a non-inlining-aware line number
# XXX - this is not inlining aware
sym = Symbols.GetLineByOffset(self.ip)
if sym is not None:
self.source_file, self.line_no = sym
else:
self.source_file = None
self.line_no = None
self.basename = None
if self.source_file is not None:
self.basename = os.path.basename(self.source_file)
else:
self.basename = None
def __str__(self):
return '{}:{}({}) {}'.format(self.basename, self.line, self.descr, self.function_name)
def main_on_stack(Symbols, frames):
module_name = Symbols.get_exefile_module_name()
main_name = "{}!main".format(module_name)
for x in frames:
if main_name in x.descr: # Could be less hard coded...
return True
return False
def probe_state(Client):
# Fetch the state of the program -- represented by the stack frames.
frames, numframes = Client.Control.GetStackTraceEx()
the_frames = [Frame(frames[x], x, Client.Symbols) for x in range(numframes)]
if not main_on_stack(Client.Symbols, the_frames):
return None
return the_frames

View File

@ -1,185 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from ctypes import *
from . import client
from . import control
from . import symbols
from .probe_process import probe_state
from .utils import *
class STARTUPINFOA(Structure):
_fields_ = [
('cb', c_ulong),
('lpReserved', c_char_p),
('lpDesktop', c_char_p),
('lpTitle', c_char_p),
('dwX', c_ulong),
('dwY', c_ulong),
('dwXSize', c_ulong),
('dwYSize', c_ulong),
('dwXCountChars', c_ulong),
('dwYCountChars', c_ulong),
('dwFillAttribute', c_ulong),
('wShowWindow', c_ushort),
('cbReserved2', c_ushort),
('lpReserved2', c_char_p),
('hStdInput', c_void_p),
('hStdOutput', c_void_p),
('hStdError', c_void_p)
]
class PROCESS_INFORMATION(Structure):
_fields_ = [
('hProcess', c_void_p),
('hThread', c_void_p),
('dwProcessId', c_ulong),
('dwThreadId', c_ulong)
]
def fetch_local_function_syms(Symbols, prefix):
syms = Symbols.get_all_functions()
def is_sym_in_src_dir(sym):
name, data = sym
symdata = Symbols.GetLineByOffset(data.Offset)
if symdata is not None:
srcfile, line = symdata
if prefix in srcfile:
return True
return False
syms = [x for x in syms if is_sym_in_src_dir(x)]
return syms
def break_on_all_but_main(Control, Symbols, main_offset):
mainfile, _ = Symbols.GetLineByOffset(main_offset)
prefix = '\\'.join(mainfile.split('\\')[:-1])
for name, rec in fetch_local_function_syms(Symbols, prefix):
if name == "main":
continue
bp = Control.AddBreakpoint2(offset=rec.Offset, enabled=True)
# All breakpoints are currently discarded: we just sys.exit for cleanup
return
def process_creator(binfile):
Kernel32 = WinDLL("Kernel32")
# Another flavour of process creation
startupinfoa = STARTUPINFOA()
startupinfoa.cb = sizeof(STARTUPINFOA)
startupinfoa.lpReserved = None
startupinfoa.lpDesktop = None
startupinfoa.lpTitle = None
startupinfoa.dwX = 0
startupinfoa.dwY = 0
startupinfoa.dwXSize = 0
startupinfoa.dwYSize = 0
startupinfoa.dwXCountChars = 0
startupinfoa.dwYCountChars = 0
startupinfoa.dwFillAttribute = 0
startupinfoa.dwFlags = 0
startupinfoa.wShowWindow = 0
startupinfoa.cbReserved2 = 0
startupinfoa.lpReserved2 = None
startupinfoa.hStdInput = None
startupinfoa.hStdOutput = None
startupinfoa.hStdError = None
processinformation = PROCESS_INFORMATION()
# 0x4 below specifies CREATE_SUSPENDED.
ret = Kernel32.CreateProcessA(binfile.encode("ascii"), None, None, None, False, 0x4, None, None, byref(startupinfoa), byref(processinformation))
if ret == 0:
raise Exception('CreateProcess running {}'.format(binfile))
return processinformation.dwProcessId, processinformation.dwThreadId, processinformation.hProcess, processinformation.hThread
def thread_resumer(hProcess, hThread):
Kernel32 = WinDLL("Kernel32")
# For reasons unclear to me, other suspend-references seem to be opened on
# the opened thread. Clear them all.
while True:
ret = Kernel32.ResumeThread(hThread)
if ret <= 0:
break
if ret < 0:
Kernel32.TerminateProcess(hProcess, 1)
raise Exception("Couldn't resume process after startup")
return
def setup_everything(binfile):
from . import client
from . import symbols
Client = client.Client()
created_pid, created_tid, hProcess, hThread = process_creator(binfile)
# Load lines as well as general symbols
sym_opts = Client.Symbols.GetSymbolOptions()
sym_opts |= symbols.SymbolOptionFlags.SYMOPT_LOAD_LINES
Client.Symbols.SetSymbolOptions(sym_opts)
Client.AttachProcess(created_pid)
# Need to enter the debugger engine to let it attach properly
Client.Control.WaitForEvent(timeout=1)
Client.SysObjects.set_current_thread(created_pid, created_tid)
Client.Control.Execute("l+t")
Client.Control.SetExpressionSyntax(cpp=True)
module_name = Client.Symbols.get_exefile_module_name()
offset = Client.Symbols.GetOffsetByName("{}!main".format(module_name))
breakpoint = Client.Control.AddBreakpoint2(offset=offset, enabled=True)
thread_resumer(hProcess, hThread)
Client.Control.SetExecutionStatus(control.DebugStatus.DEBUG_STATUS_GO)
# Problem: there is no guarantee that the client will ever reach main,
# something else exciting could happen in that time, the host system may
# be very loaded, and similar. Wait for some period, say, five seconds, and
# abort afterwards: this is a trade-off between spurious timeouts and
# completely hanging in the case of a environmental/programming error.
res = Client.Control.WaitForEvent(timeout=5000)
if res == S_FALSE:
Kernel32.TerminateProcess(hProcess, 1)
raise Exception("Debuggee did not reach main function in a timely manner")
break_on_all_but_main(Client.Control, Client.Symbols, offset)
# Set the default action on all exceptions to be "quit and detach". If we
# don't, dbgeng will merrily spin at the exception site forever.
filts = Client.Control.GetNumberEventFilters()
for x in range(filts[0], filts[0] + filts[1]):
Client.Control.SetExceptionFilterSecondCommand(x, "qd")
return Client, hProcess
def step_once(client):
client.Control.Execute("p")
try:
client.Control.WaitForEvent()
except Exception as e:
if client.Control.GetExecutionStatus() == control.DebugStatus.DEBUG_STATUS_NO_DEBUGGEE:
return None # Debuggee has gone away, likely due to an exception.
raise e
# Could assert here that we're in the "break" state
client.Control.GetExecutionStatus()
return probe_state(client)
def main_loop(client):
res = True
while res is not None:
res = step_once(client)
def cleanup(client, hProcess):
res = client.DetachProcesses()
Kernel32 = WinDLL("Kernel32")
Kernel32.TerminateProcess(hProcess, 1)

View File

@ -1,499 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from collections import namedtuple
from ctypes import *
from enum import *
from functools import reduce, partial
from .symgroup import SymbolGroup, IDebugSymbolGroup2
from .utils import *
class SymbolOptionFlags(IntFlag):
SYMOPT_CASE_INSENSITIVE = 0x00000001
SYMOPT_UNDNAME = 0x00000002
SYMOPT_DEFERRED_LOADS = 0x00000004
SYMOPT_NO_CPP = 0x00000008
SYMOPT_LOAD_LINES = 0x00000010
SYMOPT_OMAP_FIND_NEAREST = 0x00000020
SYMOPT_LOAD_ANYTHING = 0x00000040
SYMOPT_IGNORE_CVREC = 0x00000080
SYMOPT_NO_UNQUALIFIED_LOADS = 0x00000100
SYMOPT_FAIL_CRITICAL_ERRORS = 0x00000200
SYMOPT_EXACT_SYMBOLS = 0x00000400
SYMOPT_ALLOW_ABSOLUTE_SYMBOLS = 0x00000800
SYMOPT_IGNORE_NT_SYMPATH = 0x00001000
SYMOPT_INCLUDE_32BIT_MODULES = 0x00002000
SYMOPT_PUBLICS_ONLY = 0x00004000
SYMOPT_NO_PUBLICS = 0x00008000
SYMOPT_AUTO_PUBLICS = 0x00010000
SYMOPT_NO_IMAGE_SEARCH = 0x00020000
SYMOPT_SECURE = 0x00040000
SYMOPT_NO_PROMPTS = 0x00080000
SYMOPT_DEBUG = 0x80000000
class ScopeGroupFlags(IntFlag):
DEBUG_SCOPE_GROUP_ARGUMENTS = 0x00000001
DEBUG_SCOPE_GROUP_LOCALS = 0x00000002
DEBUG_SCOPE_GROUP_ALL = 0x00000003
DEBUG_SCOPE_GROUP_BY_DATAMODEL = 0x00000004
class DebugModuleNames(IntEnum):
DEBUG_MODNAME_IMAGE = 0x00000000
DEBUG_MODNAME_MODULE = 0x00000001
DEBUG_MODNAME_LOADED_IMAGE = 0x00000002
DEBUG_MODNAME_SYMBOL_FILE = 0x00000003
DEBUG_MODNAME_MAPPED_IMAGE = 0x00000004
class DebugModuleFlags(IntFlag):
DEBUG_MODULE_LOADED = 0x00000000
DEBUG_MODULE_UNLOADED = 0x00000001
DEBUG_MODULE_USER_MODE = 0x00000002
DEBUG_MODULE_EXE_MODULE = 0x00000004
DEBUG_MODULE_EXPLICIT = 0x00000008
DEBUG_MODULE_SECONDARY = 0x00000010
DEBUG_MODULE_SYNTHETIC = 0x00000020
DEBUG_MODULE_SYM_BAD_CHECKSUM = 0x00010000
class DEBUG_MODULE_PARAMETERS(Structure):
_fields_ = [
("Base", c_ulonglong),
("Size", c_ulong),
("TimeDateStamp", c_ulong),
("Checksum", c_ulong),
("Flags", c_ulong),
("SymbolType", c_ulong),
("ImageNameSize", c_ulong),
("ModuleNameSize", c_ulong),
("LoadedImageNameSize", c_ulong),
("SymbolFileNameSize", c_ulong),
("MappedImageNameSize", c_ulong),
("Reserved", c_ulonglong * 2)
]
PDEBUG_MODULE_PARAMETERS = POINTER(DEBUG_MODULE_PARAMETERS)
class DEBUG_MODULE_AND_ID(Structure):
_fields_ = [
("ModuleBase", c_ulonglong),
("Id", c_ulonglong)
]
PDEBUG_MODULE_AND_ID = POINTER(DEBUG_MODULE_AND_ID)
class DEBUG_SYMBOL_ENTRY(Structure):
_fields_ = [
("ModuleBase", c_ulonglong),
("Offset", c_ulonglong),
("Id", c_ulonglong),
("Arg64", c_ulonglong),
("Size", c_ulong),
("Flags", c_ulong),
("TypeId", c_ulong),
("NameSize", c_ulong),
("Token", c_ulong),
("Tag", c_ulong),
("Arg32", c_ulong),
("Reserved", c_ulong)
]
PDEBUG_SYMBOL_ENTRY = POINTER(DEBUG_SYMBOL_ENTRY)
# UUID for DebugSymbols5 interface.
DebugSymbols5IID = IID(0xc65fa83e, 0x1e69, 0x475e, IID_Data4_Type(0x8e, 0x0e, 0xb5, 0xd7, 0x9e, 0x9c, 0xc1, 0x7e))
class IDebugSymbols5(Structure):
pass
class IDebugSymbols5Vtbl(Structure):
wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugSymbols5))
ids_getsymboloptions = wrp(c_ulong_p)
ids_setsymboloptions = wrp(c_ulong)
ids_getmoduleparameters = wrp(c_ulong, c_ulong64_p, c_ulong, PDEBUG_MODULE_PARAMETERS)
ids_getmodulenamestring = wrp(c_ulong, c_ulong, c_ulonglong, c_char_p, c_ulong, c_ulong_p)
ids_getoffsetbyname = wrp(c_char_p, c_ulong64_p)
ids_getlinebyoffset = wrp(c_ulonglong, c_ulong_p, c_char_p, c_ulong, c_ulong_p, c_ulong64_p)
ids_getsymbolentriesbyname = wrp(c_char_p, c_ulong, PDEBUG_MODULE_AND_ID, c_ulong, c_ulong_p)
ids_getsymbolentrystring = wrp(PDEBUG_MODULE_AND_ID, c_ulong, c_char_p, c_ulong, c_ulong_p)
ids_getsymbolentryinformation = wrp(PDEBUG_MODULE_AND_ID, PDEBUG_SYMBOL_ENTRY)
ids_getcurrentscopeframeindex = wrp(c_ulong_p)
ids_getnearnamebyoffset = wrp(c_ulonglong, c_long, c_char_p, c_ulong, c_ulong_p, c_ulong64_p)
ids_setscopeframebyindex = wrp(c_ulong)
ids_getscopesymbolgroup2 = wrp(c_ulong, POINTER(IDebugSymbolGroup2), POINTER(POINTER(IDebugSymbolGroup2)))
ids_getnamebyinlinecontext = wrp(c_ulonglong, c_ulong, c_char_p, c_ulong, c_ulong_p, c_ulong64_p)
ids_getlinebyinlinecontext = wrp(c_ulonglong, c_ulong, c_ulong_p, c_char_p, c_ulong, c_ulong_p, c_ulong64_p)
_fields_ = [
("QueryInterface", c_void_p),
("AddRef", c_void_p),
("Release", c_void_p),
("GetSymbolOptions", ids_getsymboloptions),
("AddSymbolOptions", c_void_p),
("RemoveSymbolOptions", c_void_p),
("SetSymbolOptions", ids_setsymboloptions),
("GetNameByOffset", c_void_p),
("GetOffsetByName", ids_getoffsetbyname),
("GetNearNameByOffset", ids_getnearnamebyoffset),
("GetLineByOffset", ids_getlinebyoffset),
("GetOffsetByLine", c_void_p),
("GetNumberModules", c_void_p),
("GetModuleByIndex", c_void_p),
("GetModuleByModuleName", c_void_p),
("GetModuleByOffset", c_void_p),
("GetModuleNames", c_void_p),
("GetModuleParameters", ids_getmoduleparameters),
("GetSymbolModule", c_void_p),
("GetTypeName", c_void_p),
("GetTypeId", c_void_p),
("GetTypeSize", c_void_p),
("GetFieldOffset", c_void_p),
("GetSymbolTypeId", c_void_p),
("GetOffsetTypeId", c_void_p),
("ReadTypedDataVirtual", c_void_p),
("WriteTypedDataVirtual", c_void_p),
("OutputTypedDataVirtual", c_void_p),
("ReadTypedDataPhysical", c_void_p),
("WriteTypedDataPhysical", c_void_p),
("OutputTypedDataPhysical", c_void_p),
("GetScope", c_void_p),
("SetScope", c_void_p),
("ResetScope", c_void_p),
("GetScopeSymbolGroup", c_void_p),
("CreateSymbolGroup", c_void_p),
("StartSymbolMatch", c_void_p),
("GetNextSymbolMatch", c_void_p),
("EndSymbolMatch", c_void_p),
("Reload", c_void_p),
("GetSymbolPath", c_void_p),
("SetSymbolPath", c_void_p),
("AppendSymbolPath", c_void_p),
("GetImagePath", c_void_p),
("SetImagePath", c_void_p),
("AppendImagePath", c_void_p),
("GetSourcePath", c_void_p),
("GetSourcePathElement", c_void_p),
("SetSourcePath", c_void_p),
("AppendSourcePath", c_void_p),
("FindSourceFile", c_void_p),
("GetSourceFileLineOffsets", c_void_p),
("GetModuleVersionInformation", c_void_p),
("GetModuleNameString", ids_getmodulenamestring),
("GetConstantName", c_void_p),
("GetFieldName", c_void_p),
("GetTypeOptions", c_void_p),
("AddTypeOptions", c_void_p),
("RemoveTypeOptions", c_void_p),
("SetTypeOptions", c_void_p),
("GetNameByOffsetWide", c_void_p),
("GetOffsetByNameWide", c_void_p),
("GetNearNameByOffsetWide", c_void_p),
("GetLineByOffsetWide", c_void_p),
("GetOffsetByLineWide", c_void_p),
("GetModuleByModuleNameWide", c_void_p),
("GetSymbolModuleWide", c_void_p),
("GetTypeNameWide", c_void_p),
("GetTypeIdWide", c_void_p),
("GetFieldOffsetWide", c_void_p),
("GetSymbolTypeIdWide", c_void_p),
("GetScopeSymbolGroup2", ids_getscopesymbolgroup2),
("CreateSymbolGroup2", c_void_p),
("StartSymbolMatchWide", c_void_p),
("GetNextSymbolMatchWide", c_void_p),
("ReloadWide", c_void_p),
("GetSymbolPathWide", c_void_p),
("SetSymbolPathWide", c_void_p),
("AppendSymbolPathWide", c_void_p),
("GetImagePathWide", c_void_p),
("SetImagePathWide", c_void_p),
("AppendImagePathWide", c_void_p),
("GetSourcePathWide", c_void_p),
("GetSourcePathElementWide", c_void_p),
("SetSourcePathWide", c_void_p),
("AppendSourcePathWide", c_void_p),
("FindSourceFileWide", c_void_p),
("GetSourceFileLineOffsetsWide", c_void_p),
("GetModuleVersionInformationWide", c_void_p),
("GetModuleNameStringWide", c_void_p),
("GetConstantNameWide", c_void_p),
("GetFieldNameWide", c_void_p),
("IsManagedModule", c_void_p),
("GetModuleByModuleName2", c_void_p),
("GetModuleByModuleName2Wide", c_void_p),
("GetModuleByOffset2", c_void_p),
("AddSyntheticModule", c_void_p),
("AddSyntheticModuleWide", c_void_p),
("RemoveSyntheticModule", c_void_p),
("GetCurrentScopeFrameIndex", ids_getcurrentscopeframeindex),
("SetScopeFrameByIndex", ids_setscopeframebyindex),
("SetScopeFromJitDebugInfo", c_void_p),
("SetScopeFromStoredEvent", c_void_p),
("OutputSymbolByOffset", c_void_p),
("GetFunctionEntryByOffset", c_void_p),
("GetFieldTypeAndOffset", c_void_p),
("GetFieldTypeAndOffsetWide", c_void_p),
("AddSyntheticSymbol", c_void_p),
("AddSyntheticSymbolWide", c_void_p),
("RemoveSyntheticSymbol", c_void_p),
("GetSymbolEntriesByOffset", c_void_p),
("GetSymbolEntriesByName", ids_getsymbolentriesbyname),
("GetSymbolEntriesByNameWide", c_void_p),
("GetSymbolEntryByToken", c_void_p),
("GetSymbolEntryInformation", ids_getsymbolentryinformation),
("GetSymbolEntryString", ids_getsymbolentrystring),
("GetSymbolEntryStringWide", c_void_p),
("GetSymbolEntryOffsetRegions", c_void_p),
("GetSymbolEntryBySymbolEntry", c_void_p),
("GetSourceEntriesByOffset", c_void_p),
("GetSourceEntriesByLine", c_void_p),
("GetSourceEntriesByLineWide", c_void_p),
("GetSourceEntryString", c_void_p),
("GetSourceEntryStringWide", c_void_p),
("GetSourceEntryOffsetRegions", c_void_p),
("GetsourceEntryBySourceEntry", c_void_p),
("GetScopeEx", c_void_p),
("SetScopeEx", c_void_p),
("GetNameByInlineContext", ids_getnamebyinlinecontext),
("GetNameByInlineContextWide", c_void_p),
("GetLineByInlineContext", ids_getlinebyinlinecontext),
("GetLineByInlineContextWide", c_void_p),
("OutputSymbolByInlineContext", c_void_p),
("GetCurrentScopeFrameIndexEx", c_void_p),
("SetScopeFrameByIndexEx", c_void_p)
]
IDebugSymbols5._fields_ = [("lpVtbl", POINTER(IDebugSymbols5Vtbl))]
SymbolId = namedtuple("SymbolId", ["ModuleBase", "Id"])
SymbolEntry = namedtuple("SymbolEntry", ["ModuleBase", "Offset", "Id", "Arg64", "Size", "Flags", "TypeId", "NameSize", "Token", "Tag", "Arg32"])
DebugModuleParams = namedtuple("DebugModuleParams", ["Base", "Size", "TimeDateStamp", "Checksum", "Flags", "SymbolType", "ImageNameSize", "ModuleNameSize", "LoadedImageNameSize", "SymbolFileNameSize", "MappedImageNameSize"])
class SymTags(IntEnum):
Null = 0
Exe = 1
SymTagFunction = 5
def make_debug_module_params(cdata):
fieldvalues = map(lambda y: getattr(cdata, y), DebugModuleParams._fields)
return DebugModuleParams(*fieldvalues)
class Symbols(object):
def __init__(self, symbols):
self.ptr = symbols
self.symbols = symbols.contents
self.vt = self.symbols.lpVtbl.contents
# Keep some handy ulongs for passing into C methods.
self.ulong = c_ulong()
self.ulong64 = c_ulonglong()
def GetCurrentScopeFrameIndex(self):
res = self.vt.GetCurrentScopeFrameIndex(self.symbols, byref(self.ulong))
aborter(res, "GetCurrentScopeFrameIndex")
return self.ulong.value
def SetScopeFrameByIndex(self, idx):
res = self.vt.SetScopeFrameByIndex(self.symbols, idx)
aborter(res, "SetScopeFrameByIndex", ignore=[E_EINVAL])
return res != E_EINVAL
def GetOffsetByName(self, name):
res = self.vt.GetOffsetByName(self.symbols, name.encode("ascii"), byref(self.ulong64))
aborter(res, "GetOffsetByName {}".format(name))
return self.ulong64.value
def GetNearNameByOffset(self, addr):
ptr = create_string_buffer(256)
pulong = c_ulong()
disp = c_ulonglong()
# Zero arg -> "delta" indicating how many symbols to skip
res = self.vt.GetNearNameByOffset(self.symbols, addr, 0, ptr, 255, byref(pulong), byref(disp))
if res == E_NOINTERFACE:
return "{noname}"
aborter(res, "GetNearNameByOffset")
ptr[255] = '\0'.encode("ascii")
return '{}+{}'.format(string_at(ptr).decode("ascii"), disp.value)
def GetModuleByModuleName2(self, name):
# First zero arg -> module index to search from, second zero arg ->
# DEBUG_GETMOD_* flags, none of which we use.
res = self.vt.GetModuleByModuleName2(self.symbols, name, 0, 0, None, byref(self.ulong64))
aborter(res, "GetModuleByModuleName2")
return self.ulong64.value
def GetScopeSymbolGroup2(self):
retptr = POINTER(IDebugSymbolGroup2)()
res = self.vt.GetScopeSymbolGroup2(self.symbols, ScopeGroupFlags.DEBUG_SCOPE_GROUP_ALL, None, retptr)
aborter(res, "GetScopeSymbolGroup2")
return SymbolGroup(retptr)
def GetSymbolEntryString(self, idx, module):
symid = DEBUG_MODULE_AND_ID()
symid.ModuleBase = module
symid.Id = idx
ptr = create_string_buffer(1024)
# Zero arg is the string index -- symbols can have multiple names, for now
# only support the first one.
res = self.vt.GetSymbolEntryString(self.symbols, symid, 0, ptr, 1023, byref(self.ulong))
aborter(res, "GetSymbolEntryString")
return string_at(ptr).decode("ascii")
def GetSymbolEntryInformation(self, module, theid):
symid = DEBUG_MODULE_AND_ID()
symentry = DEBUG_SYMBOL_ENTRY()
symid.ModuleBase = module
symid.Id = theid
res = self.vt.GetSymbolEntryInformation(self.symbols, symid, symentry)
aborter(res, "GetSymbolEntryInformation")
# Fetch fields into SymbolEntry object
fields = map(lambda x: getattr(symentry, x), SymbolEntry._fields)
return SymbolEntry(*fields)
def GetSymbolEntriesByName(self, symstr):
# Initial query to find number of symbol entries
res = self.vt.GetSymbolEntriesByName(self.symbols, symstr.encode("ascii"), 0, None, 0, byref(self.ulong))
aborter(res, "GetSymbolEntriesByName")
# Build a buffer and query for 'length' entries
length = self.ulong.value
symrecs = (DEBUG_MODULE_AND_ID * length)()
# Zero arg -> flags, of which there are none defined.
res = self.vt.GetSymbolEntriesByName(self.symbols, symstr.encode("ascii"), 0, symrecs, length, byref(self.ulong))
aborter(res, "GetSymbolEntriesByName")
# Extract 'length' number of SymbolIds
length = self.ulong.value
def extract(x):
sym = symrecs[x]
return SymbolId(sym.ModuleBase, sym.Id)
return [extract(x) for x in range(length)]
def GetSymbolPath(self):
# Query for length of buffer to allocate
res = self.vt.GetSymbolPath(self.symbols, None, 0, byref(self.ulong))
aborter(res, "GetSymbolPath", ignore=[S_FALSE])
# Fetch 'length' length symbol path string
length = self.ulong.value
arr = create_string_buffer(length)
res = self.vt.GetSymbolPath(self.symbols, arr, length, byref(self.ulong))
aborter(res, "GetSymbolPath")
return string_at(arr).decode("ascii")
def GetSourcePath(self):
# Query for length of buffer to allocate
res = self.vt.GetSourcePath(self.symbols, None, 0, byref(self.ulong))
aborter(res, "GetSourcePath", ignore=[S_FALSE])
# Fetch a string of len 'length'
length = self.ulong.value
arr = create_string_buffer(length)
res = self.vt.GetSourcePath(self.symbols, arr, length, byref(self.ulong))
aborter(res, "GetSourcePath")
return string_at(arr).decode("ascii")
def SetSourcePath(self, string):
res = self.vt.SetSourcePath(self.symbols, string.encode("ascii"))
aborter(res, "SetSourcePath")
return
def GetModuleParameters(self, base):
self.ulong64.value = base
params = DEBUG_MODULE_PARAMETERS()
# Fetch one module params struct, starting at idx zero
res = self.vt.GetModuleParameters(self.symbols, 1, byref(self.ulong64), 0, byref(params))
aborter(res, "GetModuleParameters")
return make_debug_module_params(params)
def GetSymbolOptions(self):
res = self.vt.GetSymbolOptions(self.symbols, byref(self.ulong))
aborter(res, "GetSymbolOptions")
return SymbolOptionFlags(self.ulong.value)
def SetSymbolOptions(self, opts):
assert isinstance(opts, SymbolOptionFlags)
res = self.vt.SetSymbolOptions(self.symbols, opts.value)
aborter(res, "SetSymbolOptions")
return
def GetLineByOffset(self, offs):
# Initial query for filename buffer size
res = self.vt.GetLineByOffset(self.symbols, offs, None, None, 0, byref(self.ulong), None)
if res == E_FAIL:
return None # Sometimes we just can't get line numbers, of course
aborter(res, "GetLineByOffset", ignore=[S_FALSE])
# Allocate filename buffer and query for line number too
filenamelen = self.ulong.value
text = create_string_buffer(filenamelen)
line = c_ulong()
res = self.vt.GetLineByOffset(self.symbols, offs, byref(line), text, filenamelen, byref(self.ulong), None)
aborter(res, "GetLineByOffset")
return string_at(text).decode("ascii"), line.value
def GetModuleNameString(self, whichname, base):
# Initial query for name string length
res = self.vt.GetModuleNameString(self.symbols, whichname, DEBUG_ANY_ID, base, None, 0, byref(self.ulong))
aborter(res, "GetModuleNameString", ignore=[S_FALSE])
module_name_len = self.ulong.value
module_name = (c_char * module_name_len)()
res = self.vt.GetModuleNameString(self.symbols, whichname, DEBUG_ANY_ID, base, module_name, module_name_len, None)
aborter(res, "GetModuleNameString")
return string_at(module_name).decode("ascii")
def GetNameByInlineContext(self, pc, ctx):
# None args -> ignore output name size and displacement
buf = create_string_buffer(256)
res = self.vt.GetNameByInlineContext(self.symbols, pc, ctx, buf, 255, None, None)
aborter(res, "GetNameByInlineContext")
return string_at(buf).decode("ascii")
def GetLineByInlineContext(self, pc, ctx):
# None args -> ignore output filename size and displacement
buf = create_string_buffer(256)
res = self.vt.GetLineByInlineContext(self.symbols, pc, ctx, byref(self.ulong), buf, 255, None, None)
aborter(res, "GetLineByInlineContext")
return string_at(buf).decode("ascii"), self.ulong.value
def get_all_symbols(self):
main_module_name = self.get_exefile_module_name()
idnumbers = self.GetSymbolEntriesByName("{}!*".format(main_module_name))
lst = []
for symid in idnumbers:
s = self.GetSymbolEntryString(symid.Id, symid.ModuleBase)
symentry = self.GetSymbolEntryInformation(symid.ModuleBase, symid.Id)
lst.append((s, symentry))
return lst
def get_all_functions(self):
syms = self.get_all_symbols()
return [x for x in syms if x[1].Tag == SymTags.SymTagFunction]
def get_all_modules(self):
params = DEBUG_MODULE_PARAMETERS()
idx = 0
res = 0
all_modules = []
while res != E_EINVAL:
res = self.vt.GetModuleParameters(self.symbols, 1, None, idx, byref(params))
aborter(res, "GetModuleParameters", ignore=[E_EINVAL])
all_modules.append(make_debug_module_params(params))
idx += 1
return all_modules
def get_exefile_module(self):
all_modules = self.get_all_modules()
reduce_func = lambda x, y: y if y.Flags & DebugModuleFlags.DEBUG_MODULE_EXE_MODULE else x
main_module = reduce(reduce_func, all_modules, None)
if main_module is None:
raise Exception("Couldn't find the exefile module")
return main_module
def get_module_name(self, base):
return self.GetModuleNameString(DebugModuleNames.DEBUG_MODNAME_MODULE, base)
def get_exefile_module_name(self):
return self.get_module_name(self.get_exefile_module().Base)

View File

@ -1,98 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from collections import namedtuple
from ctypes import *
from functools import partial
from .utils import *
Symbol = namedtuple("Symbol", ["num", "name", "type", "value"])
class IDebugSymbolGroup2(Structure):
pass
class IDebugSymbolGroup2Vtbl(Structure):
wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugSymbolGroup2))
ids_getnumbersymbols = wrp(c_ulong_p)
ids_getsymbolname = wrp(c_ulong, c_char_p, c_ulong, c_ulong_p)
ids_getsymboltypename = wrp(c_ulong, c_char_p, c_ulong, c_ulong_p)
ids_getsymbolvaluetext = wrp(c_ulong, c_char_p, c_ulong, c_ulong_p)
_fields_ = [
("QueryInterface", c_void_p),
("AddRef", c_void_p),
("Release", c_void_p),
("GetNumberSymbols", ids_getnumbersymbols),
("AddSymbol", c_void_p),
("RemoveSymbolByName", c_void_p),
("RemoveSymbolByIndex", c_void_p),
("GetSymbolName", ids_getsymbolname),
("GetSymbolParameters", c_void_p),
("ExpandSymbol", c_void_p),
("OutputSymbols", c_void_p),
("WriteSymbol", c_void_p),
("OutputAsType", c_void_p),
("AddSymbolWide", c_void_p),
("RemoveSymbolByNameWide", c_void_p),
("GetSymbolNameWide", c_void_p),
("WritesymbolWide", c_void_p),
("OutputAsTypeWide", c_void_p),
("GetSymbolTypeName", ids_getsymboltypename),
("GetSymbolTypeNameWide", c_void_p),
("GetSymbolSize", c_void_p),
("GetSymbolOffset", c_void_p),
("GetSymbolRegister", c_void_p),
("GetSymbolValueText", ids_getsymbolvaluetext),
("GetSymbolValueTextWide", c_void_p),
("GetSymbolEntryInformation", c_void_p)
]
IDebugSymbolGroup2._fields_ = [("lpVtbl", POINTER(IDebugSymbolGroup2Vtbl))]
class SymbolGroup(object):
def __init__(self, symgroup):
self.symgroup = symgroup.contents
self.vt = self.symgroup.lpVtbl.contents
self.ulong = c_ulong()
def GetNumberSymbols(self):
res = self.vt.GetNumberSymbols(self.symgroup, byref(self.ulong))
aborter(res, "GetNumberSymbols")
return self.ulong.value
def GetSymbolName(self, idx):
buf = create_string_buffer(256)
res = self.vt.GetSymbolName(self.symgroup, idx, buf, 255, byref(self.ulong))
aborter(res, "GetSymbolName")
thelen = self.ulong.value
return string_at(buf).decode("ascii")
def GetSymbolTypeName(self, idx):
buf = create_string_buffer(256)
res = self.vt.GetSymbolTypeName(self.symgroup, idx, buf, 255, byref(self.ulong))
aborter(res, "GetSymbolTypeName")
thelen = self.ulong.value
return string_at(buf).decode("ascii")
def GetSymbolValueText(self, idx, handleserror=False):
buf = create_string_buffer(256)
res = self.vt.GetSymbolValueText(self.symgroup, idx, buf, 255, byref(self.ulong))
if res != 0 and handleserror:
return None
aborter(res, "GetSymbolTypeName")
thelen = self.ulong.value
return string_at(buf).decode("ascii")
def get_symbol(self, idx):
name = self.GetSymbolName(idx)
thetype = self.GetSymbolTypeName(idx)
value = self.GetSymbolValueText(idx)
return Symbol(idx, name, thetype, value)
def get_all_symbols(self):
num_syms = self.GetNumberSymbols()
return list(map(self.get_symbol, list(range(num_syms))))

View File

@ -1,200 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from ctypes import *
from functools import partial
from .utils import *
# UUID For SystemObjects4 interface.
DebugSystemObjects4IID = IID(0x489468e6, 0x7d0f, 0x4af5, IID_Data4_Type(0x87, 0xab, 0x25, 0x20, 0x74, 0x54, 0xd5, 0x53))
class IDebugSystemObjects4(Structure):
pass
class IDebugSystemObjects4Vtbl(Structure):
wrp = partial(WINFUNCTYPE, c_long, POINTER(IDebugSystemObjects4))
ids_getnumberprocesses = wrp(POINTER(c_ulong))
ids_getprocessidsbyindex = wrp(c_ulong, c_ulong, c_ulong_p, c_ulong_p)
ids_setcurrentprocessid = wrp(c_ulong)
ids_getnumberthreads = wrp(c_ulong_p)
ids_getthreadidsbyindex = wrp(c_ulong, c_ulong, c_ulong_p, c_ulong_p)
ids_setcurrentthreadid = wrp(c_ulong)
_fields_ = [
("QueryInterface", c_void_p),
("AddRef", c_void_p),
("Release", c_void_p),
("GetEventThread", c_void_p),
("GetEventProcess", c_void_p),
("GetCurrentThreadId", c_void_p),
("SetCurrentThreadId", ids_setcurrentthreadid),
("GetCurrentProcessId", c_void_p),
("SetCurrentProcessId", ids_setcurrentprocessid),
("GetNumberThreads", ids_getnumberthreads),
("GetTotalNumberThreads", c_void_p),
("GetThreadIdsByIndex", ids_getthreadidsbyindex),
("GetThreadIdByProcessor", c_void_p),
("GetCurrentThreadDataOffset", c_void_p),
("GetThreadIdByDataOffset", c_void_p),
("GetCurrentThreadTeb", c_void_p),
("GetThreadIdByTeb", c_void_p),
("GetCurrentThreadSystemId", c_void_p),
("GetThreadIdBySystemId", c_void_p),
("GetCurrentThreadHandle", c_void_p),
("GetThreadIdByHandle", c_void_p),
("GetNumberProcesses", ids_getnumberprocesses),
("GetProcessIdsByIndex", ids_getprocessidsbyindex),
("GetCurrentProcessDataOffset", c_void_p),
("GetProcessIdByDataOffset", c_void_p),
("GetCurrentProcessPeb", c_void_p),
("GetProcessIdByPeb", c_void_p),
("GetCurrentProcessSystemId", c_void_p),
("GetProcessIdBySystemId", c_void_p),
("GetCurrentProcessHandle", c_void_p),
("GetProcessIdByHandle", c_void_p),
("GetCurrentProcessExecutableName", c_void_p),
("GetCurrentProcessUpTime", c_void_p),
("GetImplicitThreadDataOffset", c_void_p),
("SetImplicitThreadDataOffset", c_void_p),
("GetImplicitProcessDataOffset", c_void_p),
("SetImplicitProcessDataOffset", c_void_p),
("GetEventSystem", c_void_p),
("GetCurrentSystemId", c_void_p),
("SetCurrentSystemId", c_void_p),
("GetNumberSystems", c_void_p),
("GetSystemIdsByIndex", c_void_p),
("GetTotalNumberThreadsAndProcesses", c_void_p),
("GetCurrentSystemServer", c_void_p),
("GetSystemByServer", c_void_p),
("GetCurrentSystemServerName", c_void_p),
("GetCurrentProcessExecutableNameWide", c_void_p),
("GetCurrentSystemServerNameWide", c_void_p)
]
IDebugSystemObjects4._fields_ = [("lpVtbl", POINTER(IDebugSystemObjects4Vtbl))]
class SysObjects(object):
def __init__(self, sysobjects):
self.ptr = sysobjects
self.sysobjects = sysobjects.contents
self.vt = self.sysobjects.lpVtbl.contents
# Keep a handy ulong for passing into C methods.
self.ulong = c_ulong()
def GetNumberSystems(self):
res = self.vt.GetNumberSystems(self.sysobjects, byref(self.ulong))
aborter(res, "GetNumberSystems")
return self.ulong.value
def GetNumberProcesses(self):
res = self.vt.GetNumberProcesses(self.sysobjects, byref(self.ulong))
aborter(res, "GetNumberProcesses")
return self.ulong.value
def GetNumberThreads(self):
res = self.vt.GetNumberThreads(self.sysobjects, byref(self.ulong))
aborter(res, "GetNumberThreads")
return self.ulong.value
def GetTotalNumberThreadsAndProcesses(self):
tthreads = c_ulong()
tprocs = c_ulong()
pulong3 = c_ulong()
res = self.vt.GetTotalNumberThreadsAndProcesses(self.sysobjects, byref(tthreads), byref(tprocs), byref(pulong3), byref(pulong3), byref(pulong3))
aborter(res, "GettotalNumberThreadsAndProcesses")
return tthreads.value, tprocs.value
def GetCurrentProcessId(self):
res = self.vt.GetCurrentProcessId(self.sysobjects, byref(self.ulong))
aborter(res, "GetCurrentProcessId")
return self.ulong.value
def SetCurrentProcessId(self, sysid):
res = self.vt.SetCurrentProcessId(self.sysobjects, sysid)
aborter(res, "SetCurrentProcessId")
return
def GetCurrentThreadId(self):
res = self.vt.GetCurrentThreadId(self.sysobjects, byref(self.ulong))
aborter(res, "GetCurrentThreadId")
return self.ulong.value
def SetCurrentThreadId(self, sysid):
res = self.vt.SetCurrentThreadId(self.sysobjects, sysid)
aborter(res, "SetCurrentThreadId")
return
def GetProcessIdsByIndex(self):
num_processes = self.GetNumberProcesses()
if num_processes == 0:
return []
engineids = (c_ulong * num_processes)()
pids = (c_ulong * num_processes)()
for x in range(num_processes):
engineids[x] = DEBUG_ANY_ID
pids[x] = DEBUG_ANY_ID
res = self.vt.GetProcessIdsByIndex(self.sysobjects, 0, num_processes, engineids, pids)
aborter(res, "GetProcessIdsByIndex")
return list(zip(engineids, pids))
def GetThreadIdsByIndex(self):
num_threads = self.GetNumberThreads()
if num_threads == 0:
return []
engineids = (c_ulong * num_threads)()
tids = (c_ulong * num_threads)()
for x in range(num_threads):
engineids[x] = DEBUG_ANY_ID
tids[x] = DEBUG_ANY_ID
# Zero -> start index
res = self.vt.GetThreadIdsByIndex(self.sysobjects, 0, num_threads, engineids, tids)
aborter(res, "GetThreadIdsByIndex")
return list(zip(engineids, tids))
def GetCurThreadHandle(self):
pulong64 = c_ulonglong()
res = self.vt.GetCurrentThreadHandle(self.sysobjects, byref(pulong64))
aborter(res, "GetCurrentThreadHandle")
return pulong64.value
def set_current_thread(self, pid, tid):
proc_sys_id = -1
for x in self.GetProcessIdsByIndex():
sysid, procid = x
if procid == pid:
proc_sys_id = sysid
if proc_sys_id == -1:
raise Exception("Couldn't find designated PID {}".format(pid))
self.SetCurrentProcessId(proc_sys_id)
thread_sys_id = -1
for x in self.GetThreadIdsByIndex():
sysid, threadid = x
if threadid == tid:
thread_sys_id = sysid
if thread_sys_id == -1:
raise Exception("Couldn't find designated TID {}".format(tid))
self.SetCurrentThreadId(thread_sys_id)
return
def print_current_procs_threads(self):
procs = []
for x in self.GetProcessIdsByIndex():
sysid, procid = x
procs.append(procid)
threads = []
for x in self.GetThreadIdsByIndex():
sysid, threadid = x
threads.append(threadid)
print("Current processes: {}".format(procs))
print("Current threads: {}".format(threads))

View File

@ -1,47 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from ctypes import *
# Error codes are negative when received by python, but are typically
# represented by unsigned hex elsewhere. Subtract 2^32 from the unsigned
# hex to produce negative error codes.
E_NOINTERFACE = 0x80004002 - 0x100000000
E_FAIL = 0x80004005 - 0x100000000
E_EINVAL = 0x80070057 - 0x100000000
E_INTERNALEXCEPTION = 0x80040205 - 0x100000000
S_FALSE = 1
# This doesn't fit into any convenient category
DEBUG_ANY_ID = 0xFFFFFFFF
class WinError(Exception):
def __init__(self, msg, hstatus):
self.hstatus = hstatus
super(WinError, self).__init__(msg)
def aborter(res, msg, ignore=[]):
if res != 0 and res not in ignore:
# Convert a negative error code to a positive unsigned one, which is
# now NTSTATUSes appear in documentation.
if res < 0:
res += 0x100000000
msg = '{:08X} : {}'.format(res, msg)
raise WinError(msg, res)
IID_Data4_Type = c_ubyte * 8
class IID(Structure):
_fields_ = [
("Data1", c_uint),
("Data2", c_ushort),
("Data3", c_ushort),
("Data4", IID_Data4_Type)
]
c_ulong_p = POINTER(c_ulong)
c_ulong64_p = POINTER(c_ulonglong)

View File

@ -1,244 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Interface for communicating with the LLDB debugger via its python interface.
"""
import imp
import os
from subprocess import CalledProcessError, check_output, STDOUT
import sys
from dex.debugger.DebuggerBase import DebuggerBase
from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR
from dex.dextIR import StackFrame, SourceLocation, ProgramState
from dex.utils.Exceptions import DebuggerException, LoadDebuggerException
from dex.utils.ReturnCode import ReturnCode
class LLDB(DebuggerBase):
def __init__(self, context, *args):
self.lldb_executable = context.options.lldb_executable
self._debugger = None
self._target = None
self._process = None
self._thread = None
super(LLDB, self).__init__(context, *args)
def _custom_init(self):
self._debugger = self._interface.SBDebugger.Create()
self._debugger.SetAsync(False)
self._target = self._debugger.CreateTargetWithFileAndArch(
self.context.options.executable, self.context.options.arch)
if not self._target:
raise LoadDebuggerException(
'could not create target for executable "{}" with arch:{}'.
format(self.context.options.executable,
self.context.options.arch))
def _custom_exit(self):
if getattr(self, '_process', None):
self._process.Kill()
if getattr(self, '_debugger', None) and getattr(self, '_target', None):
self._debugger.DeleteTarget(self._target)
def _translate_stop_reason(self, reason):
if reason == self._interface.eStopReasonNone:
return None
if reason == self._interface.eStopReasonBreakpoint:
return StopReason.BREAKPOINT
if reason == self._interface.eStopReasonPlanComplete:
return StopReason.STEP
if reason == self._interface.eStopReasonThreadExiting:
return StopReason.PROGRAM_EXIT
if reason == self._interface.eStopReasonException:
return StopReason.ERROR
return StopReason.OTHER
def _load_interface(self):
try:
args = [self.lldb_executable, '-P']
pythonpath = check_output(
args, stderr=STDOUT).rstrip().decode('utf-8')
except CalledProcessError as e:
raise LoadDebuggerException(str(e), sys.exc_info())
except OSError as e:
raise LoadDebuggerException(
'{} ["{}"]'.format(e.strerror, self.lldb_executable),
sys.exc_info())
if not os.path.isdir(pythonpath):
raise LoadDebuggerException(
'path "{}" does not exist [result of {}]'.format(
pythonpath, args), sys.exc_info())
try:
module_info = imp.find_module('lldb', [pythonpath])
return imp.load_module('lldb', *module_info)
except ImportError as e:
msg = str(e)
if msg.endswith('not a valid Win32 application.'):
msg = '{} [Are you mixing 32-bit and 64-bit binaries?]'.format(
msg)
raise LoadDebuggerException(msg, sys.exc_info())
@classmethod
def get_name(cls):
return 'lldb'
@classmethod
def get_option_name(cls):
return 'lldb'
@property
def version(self):
try:
return self._interface.SBDebugger_GetVersionString()
except AttributeError:
return None
def clear_breakpoints(self):
self._target.DeleteAllBreakpoints()
def add_breakpoint(self, file_, line):
if not self._target.BreakpointCreateByLocation(file_, line):
raise LoadDebuggerException(
'could not add breakpoint [{}:{}]'.format(file_, line))
def launch(self):
self._process = self._target.LaunchSimple(None, None, os.getcwd())
if not self._process or self._process.GetNumThreads() == 0:
raise DebuggerException('could not launch process')
if self._process.GetNumThreads() != 1:
raise DebuggerException('multiple threads not supported')
self._thread = self._process.GetThreadAtIndex(0)
assert self._thread, (self._process, self._thread)
def step(self):
self._thread.StepInto()
def go(self) -> ReturnCode:
self._process.Continue()
return ReturnCode.OK
def get_step_info(self):
frames = []
state_frames = []
for i in range(0, self._thread.GetNumFrames()):
sb_frame = self._thread.GetFrameAtIndex(i)
sb_line = sb_frame.GetLineEntry()
sb_filespec = sb_line.GetFileSpec()
try:
path = os.path.join(sb_filespec.GetDirectory(),
sb_filespec.GetFilename())
except (AttributeError, TypeError):
path = None
function = self._sanitize_function_name(sb_frame.GetFunctionName())
loc_dict = {
'path': path,
'lineno': sb_line.GetLine(),
'column': sb_line.GetColumn()
}
loc = LocIR(**loc_dict)
frame = FrameIR(
function=function, is_inlined=sb_frame.IsInlined(), loc=loc)
if any(
name in (frame.function or '') # pylint: disable=no-member
for name in self.frames_below_main):
break
frames.append(frame)
state_frame = StackFrame(function=frame.function,
is_inlined=frame.is_inlined,
location=SourceLocation(**loc_dict),
watches={})
for expr in map(
lambda watch, idx=i: self.evaluate_expression(watch, idx),
self.watches):
state_frame.watches[expr.expression] = expr
state_frames.append(state_frame)
if len(frames) == 1 and frames[0].function is None:
frames = []
state_frames = []
reason = self._translate_stop_reason(self._thread.GetStopReason())
return StepIR(
step_index=self.step_index, frames=frames, stop_reason=reason,
program_state=ProgramState(state_frames))
@property
def is_running(self):
# We're not running in async mode so this is always False.
return False
@property
def is_finished(self):
return not self._thread.GetFrameAtIndex(0)
@property
def frames_below_main(self):
return ['__scrt_common_main_seh', '__libc_start_main']
def evaluate_expression(self, expression, frame_idx=0) -> ValueIR:
result = self._thread.GetFrameAtIndex(frame_idx
).EvaluateExpression(expression)
error_string = str(result.error)
value = result.value
could_evaluate = not any(s in error_string for s in [
"Can't run the expression locally",
"use of undeclared identifier",
"no member named",
"Couldn't lookup symbols",
"reference to local variable",
"invalid use of 'this' outside of a non-static member function",
])
is_optimized_away = any(s in error_string for s in [
'value may have been optimized out',
])
is_irretrievable = any(s in error_string for s in [
"couldn't get the value of variable",
"couldn't read its memory",
"couldn't read from memory",
"Cannot access memory at address",
"invalid address (fault address:",
])
if could_evaluate and not is_irretrievable and not is_optimized_away:
assert error_string == 'success', (error_string, expression, value)
# assert result.value is not None, (result.value, expression)
if error_string == 'success':
error_string = None
# attempt to find expression as a variable, if found, take the variable
# obj's type information as it's 'usually' more accurate.
var_result = self._thread.GetFrameAtIndex(frame_idx).FindVariable(expression)
if str(var_result.error) == 'success':
type_name = var_result.type.GetDisplayTypeName()
else:
type_name = result.type.GetDisplayTypeName()
return ValueIR(
expression=expression,
value=value,
type_name=type_name,
error_string=error_string,
could_evaluate=could_evaluate,
is_optimized_away=is_optimized_away,
is_irretrievable=is_irretrievable,
)

View File

@ -1,8 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.debugger.lldb.LLDB import LLDB

View File

@ -1,224 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Interface for communicating with the Visual Studio debugger via DTE."""
import abc
import imp
import os
import sys
from dex.debugger.DebuggerBase import DebuggerBase
from dex.dextIR import FrameIR, LocIR, StepIR, StopReason, ValueIR
from dex.dextIR import StackFrame, SourceLocation, ProgramState
from dex.utils.Exceptions import Error, LoadDebuggerException
from dex.utils.ReturnCode import ReturnCode
def _load_com_module():
try:
module_info = imp.find_module(
'ComInterface',
[os.path.join(os.path.dirname(__file__), 'windows')])
return imp.load_module('ComInterface', *module_info)
except ImportError as e:
raise LoadDebuggerException(e, sys.exc_info())
class VisualStudio(DebuggerBase, metaclass=abc.ABCMeta): # pylint: disable=abstract-method
# Constants for results of Debugger.CurrentMode
# (https://msdn.microsoft.com/en-us/library/envdte.debugger.currentmode.aspx)
dbgDesignMode = 1
dbgBreakMode = 2
dbgRunMode = 3
def __init__(self, *args):
self.com_module = None
self._debugger = None
self._solution = None
self._fn_step = None
self._fn_go = None
super(VisualStudio, self).__init__(*args)
def _custom_init(self):
try:
self._debugger = self._interface.Debugger
self._debugger.HexDisplayMode = False
self._interface.MainWindow.Visible = (
self.context.options.show_debugger)
self._solution = self._interface.Solution
self._solution.Create(self.context.working_directory.path,
'DexterSolution')
try:
self._solution.AddFromFile(self._project_file)
except OSError:
raise LoadDebuggerException(
'could not debug the specified executable', sys.exc_info())
self._fn_step = self._debugger.StepInto
self._fn_go = self._debugger.Go
except AttributeError as e:
raise LoadDebuggerException(str(e), sys.exc_info())
def _custom_exit(self):
if self._interface:
self._interface.Quit()
@property
def _project_file(self):
return self.context.options.executable
@abc.abstractproperty
def _dte_version(self):
pass
@property
def _location(self):
bp = self._debugger.BreakpointLastHit
return {
'path': getattr(bp, 'File', None),
'lineno': getattr(bp, 'FileLine', None),
'column': getattr(bp, 'FileColumn', None)
}
@property
def _mode(self):
return self._debugger.CurrentMode
def _load_interface(self):
self.com_module = _load_com_module()
return self.com_module.DTE(self._dte_version)
@property
def version(self):
try:
return self._interface.Version
except AttributeError:
return None
def clear_breakpoints(self):
for bp in self._debugger.Breakpoints:
bp.Delete()
def add_breakpoint(self, file_, line):
self._debugger.Breakpoints.Add('', file_, line)
def launch(self):
self.step()
def step(self):
self._fn_step()
def go(self) -> ReturnCode:
self._fn_go()
return ReturnCode.OK
def set_current_stack_frame(self, idx: int = 0):
thread = self._debugger.CurrentThread
stack_frames = thread.StackFrames
try:
stack_frame = stack_frames[idx]
self._debugger.CurrentStackFrame = stack_frame.raw
except IndexError:
raise Error('attempted to access stack frame {} out of {}'
.format(idx, len(stack_frames)))
def get_step_info(self):
thread = self._debugger.CurrentThread
stackframes = thread.StackFrames
frames = []
state_frames = []
for idx, sf in enumerate(stackframes):
frame = FrameIR(
function=self._sanitize_function_name(sf.FunctionName),
is_inlined=sf.FunctionName.startswith('[Inline Frame]'),
loc=LocIR(path=None, lineno=None, column=None))
fname = frame.function or '' # pylint: disable=no-member
if any(name in fname for name in self.frames_below_main):
break
state_frame = StackFrame(function=frame.function,
is_inlined=frame.is_inlined,
watches={})
for watch in self.watches:
state_frame.watches[watch] = self.evaluate_expression(
watch, idx)
state_frames.append(state_frame)
frames.append(frame)
loc = LocIR(**self._location)
if frames:
frames[0].loc = loc
state_frames[0].location = SourceLocation(**self._location)
reason = StopReason.BREAKPOINT
if loc.path is None: # pylint: disable=no-member
reason = StopReason.STEP
program_state = ProgramState(frames=state_frames)
return StepIR(
step_index=self.step_index, frames=frames, stop_reason=reason,
program_state=program_state)
@property
def is_running(self):
return self._mode == VisualStudio.dbgRunMode
@property
def is_finished(self):
return self._mode == VisualStudio.dbgDesignMode
@property
def frames_below_main(self):
return [
'[Inline Frame] invoke_main', '__scrt_common_main_seh',
'__tmainCRTStartup', 'mainCRTStartup'
]
def evaluate_expression(self, expression, frame_idx=0) -> ValueIR:
self.set_current_stack_frame(frame_idx)
result = self._debugger.GetExpression(expression)
self.set_current_stack_frame(0)
value = result.Value
is_optimized_away = any(s in value for s in [
'Variable is optimized away and not available',
'Value is not available, possibly due to optimization',
])
is_irretrievable = any(s in value for s in [
'???',
'<Unable to read memory>',
])
# an optimized away value is still counted as being able to be
# evaluated.
could_evaluate = (result.IsValidValue or is_optimized_away
or is_irretrievable)
return ValueIR(
expression=expression,
value=value,
type_name=result.Type,
error_string=None,
is_optimized_away=is_optimized_away,
could_evaluate=could_evaluate,
is_irretrievable=is_irretrievable,
)

View File

@ -1,23 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Specializations for the Visual Studio 2015 interface."""
from dex.debugger.visualstudio.VisualStudio import VisualStudio
class VisualStudio2015(VisualStudio):
@classmethod
def get_name(cls):
return 'Visual Studio 2015'
@classmethod
def get_option_name(cls):
return 'vs2015'
@property
def _dte_version(self):
return 'VisualStudio.DTE.14.0'

View File

@ -1,23 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Specializations for the Visual Studio 2017 interface."""
from dex.debugger.visualstudio.VisualStudio import VisualStudio
class VisualStudio2017(VisualStudio):
@classmethod
def get_name(cls):
return 'Visual Studio 2017'
@classmethod
def get_option_name(cls):
return 'vs2017'
@property
def _dte_version(self):
return 'VisualStudio.DTE.15.0'

View File

@ -1,9 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.debugger.visualstudio.VisualStudio2015 import VisualStudio2015
from dex.debugger.visualstudio.VisualStudio2017 import VisualStudio2017

View File

@ -1,119 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Communication via the Windows COM interface."""
import inspect
import time
import sys
# pylint: disable=import-error
import win32com.client as com
import win32api
# pylint: enable=import-error
from dex.utils.Exceptions import LoadDebuggerException
_com_error = com.pywintypes.com_error # pylint: disable=no-member
def get_file_version(file_):
try:
info = win32api.GetFileVersionInfo(file_, '\\')
ms = info['FileVersionMS']
ls = info['FileVersionLS']
return '.'.join(
str(s) for s in [
win32api.HIWORD(ms),
win32api.LOWORD(ms),
win32api.HIWORD(ls),
win32api.LOWORD(ls)
])
except com.pywintypes.error: # pylint: disable=no-member
return 'no versioninfo present'
def _handle_com_error(e):
exc = sys.exc_info()
msg = win32api.FormatMessage(e.hresult)
try:
msg = msg.decode('CP1251')
except AttributeError:
pass
msg = msg.strip()
return msg, exc
class ComObject(object):
"""Wrap a raw Windows COM object in a class that implements auto-retry of
failed calls.
"""
def __init__(self, raw):
assert not isinstance(raw, ComObject), raw
self.__dict__['raw'] = raw
def __str__(self):
return self._call(self.raw.__str__)
def __getattr__(self, key):
if key in self.__dict__:
return self.__dict__[key]
return self._call(self.raw.__getattr__, key)
def __setattr__(self, key, val):
if key in self.__dict__:
self.__dict__[key] = val
self._call(self.raw.__setattr__, key, val)
def __getitem__(self, key):
return self._call(self.raw.__getitem__, key)
def __setitem__(self, key, val):
self._call(self.raw.__setitem__, key, val)
def __call__(self, *args):
return self._call(self.raw, *args)
@classmethod
def _call(cls, fn, *args):
"""COM calls tend to randomly fail due to thread sync issues.
The Microsoft recommended solution is to set up a message filter object
to automatically retry failed calls, but this seems prohibitively hard
from python, so this is a custom solution to do the same thing.
All COM accesses should go through this function.
"""
ex = AssertionError("this should never be raised!")
assert (inspect.isfunction(fn) or inspect.ismethod(fn)
or inspect.isbuiltin(fn)), (fn, type(fn))
retries = ([0] * 50) + ([1] * 5)
for r in retries:
try:
try:
result = fn(*args)
if inspect.ismethod(result) or 'win32com' in str(
result.__class__):
result = ComObject(result)
return result
except _com_error as e:
msg, _ = _handle_com_error(e)
e = WindowsError(msg) # pylint: disable=undefined-variable
raise e
except (AttributeError, TypeError, OSError) as e:
ex = e
time.sleep(r)
raise ex
class DTE(ComObject):
def __init__(self, class_string):
try:
super(DTE, self).__init__(com.DispatchEx(class_string))
except _com_error as e:
msg, exc = _handle_com_error(e)
raise LoadDebuggerException(
'{} [{}]'.format(msg, class_string), orig_exception=exc)

View File

@ -1,6 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

View File

@ -1,16 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
class BuilderIR:
"""Data class which represents the compiler related options passed to Dexter
"""
def __init__(self, name: str, cflags: str, ldflags: str):
self.name = name
self.cflags = cflags
self.ldflags = ldflags

View File

@ -1,14 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
class DebuggerIR:
"""Data class which represents a debugger."""
def __init__(self, name: str, version: str):
self.name = name
self.version = version

View File

@ -1,129 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from collections import OrderedDict
import os
from typing import List
from dex.dextIR.BuilderIR import BuilderIR
from dex.dextIR.DebuggerIR import DebuggerIR
from dex.dextIR.StepIR import StepIR, StepKind
def _step_kind_func(context, step):
if (step.current_location.path is None or
not os.path.exists(step.current_location.path)):
return StepKind.FUNC_UNKNOWN
if any(os.path.samefile(step.current_location.path, f)
for f in context.options.source_files):
return StepKind.FUNC
return StepKind.FUNC_EXTERNAL
class DextIR:
"""A full Dexter test report.
This is composed of all the other *IR classes. They are used together to
record Dexter inputs and the resultant debugger steps, providing a single
high level access container.
The Heuristic class works with dexter commands and the generated DextIR to
determine the debugging score for a given test.
Args:
commands: { name (str), commands (list[CommandIR])
"""
def __init__(self,
dexter_version: str,
executable_path: str,
source_paths: List[str],
builder: BuilderIR = None,
debugger: DebuggerIR = None,
commands: OrderedDict = None):
self.dexter_version = dexter_version
self.executable_path = executable_path
self.source_paths = source_paths
self.builder = builder
self.debugger = debugger
self.commands = commands
self.steps: List[StepIR] = []
def __str__(self):
colors = 'rgby'
st = '## BEGIN ##\n'
color_idx = 0
for step in self.steps:
if step.step_kind in (StepKind.FUNC, StepKind.FUNC_EXTERNAL,
StepKind.FUNC_UNKNOWN):
color_idx += 1
color = colors[color_idx % len(colors)]
st += '<{}>{}</>\n'.format(color, step)
st += '## END ({} step{}) ##\n'.format(
self.num_steps, '' if self.num_steps == 1 else 's')
return st
@property
def num_steps(self):
return len(self.steps)
def _get_prev_step_in_this_frame(self, step):
"""Find the most recent step in the same frame as `step`.
Returns:
StepIR or None if there is no previous step in this frame.
"""
return next((s for s in reversed(self.steps)
if s.current_function == step.current_function
and s.num_frames == step.num_frames), None)
def _get_new_step_kind(self, context, step):
if step.current_function is None:
return StepKind.UNKNOWN
if len(self.steps) == 0:
return _step_kind_func(context, step)
prev_step = self.steps[-1]
if prev_step.current_function is None:
return StepKind.UNKNOWN
if prev_step.num_frames < step.num_frames:
return _step_kind_func(context, step)
if prev_step.num_frames > step.num_frames:
frame_step = self._get_prev_step_in_this_frame(step)
prev_step = frame_step if frame_step is not None else prev_step
# We're in the same func as prev step, check lineo.
if prev_step.current_location.lineno > step.current_location.lineno:
return StepKind.VERTICAL_BACKWARD
if prev_step.current_location.lineno < step.current_location.lineno:
return StepKind.VERTICAL_FORWARD
# We're on the same line as prev step, check column.
if prev_step.current_location.column > step.current_location.column:
return StepKind.HORIZONTAL_BACKWARD
if prev_step.current_location.column < step.current_location.column:
return StepKind.HORIZONTAL_FORWARD
# This step is in exactly the same location as the prev step.
return StepKind.SAME
def new_step(self, context, step):
assert isinstance(step, StepIR), type(step)
step.step_kind = self._get_new_step_kind(context, step)
self.steps.append(step)
return step
def clear_steps(self):
self.steps.clear()

View File

@ -1,16 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.dextIR.LocIR import LocIR
class FrameIR:
"""Data class which represents a frame in the call stack"""
def __init__(self, function: str, is_inlined: bool, loc: LocIR):
self.function = function
self.is_inlined = is_inlined
self.loc = loc

View File

@ -1,45 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
import os
class LocIR:
"""Data class which represents a source location."""
def __init__(self, path: str, lineno: int, column: int):
if path:
path = os.path.normcase(path)
self.path = path
self.lineno = lineno
self.column = column
def __str__(self):
return '{}({}:{})'.format(self.path, self.lineno, self.column)
def __eq__(self, rhs):
return (os.path.exists(self.path) and os.path.exists(rhs.path)
and os.path.samefile(self.path, rhs.path)
and self.lineno == rhs.lineno
and self.column == rhs.column)
def __lt__(self, rhs):
if self.path != rhs.path:
return False
if self.lineno == rhs.lineno:
return self.column < rhs.column
return self.lineno < rhs.lineno
def __gt__(self, rhs):
if self.path != rhs.path:
return False
if self.lineno == rhs.lineno:
return self.column > rhs.column
return self.lineno > rhs.lineno

View File

@ -1,117 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Set of data classes for representing the complete debug program state at a
fixed point in execution.
"""
import os
from collections import OrderedDict
from typing import List
class SourceLocation:
def __init__(self, path: str = None, lineno: int = None, column: int = None):
if path:
path = os.path.normcase(path)
self.path = path
self.lineno = lineno
self.column = column
def __str__(self):
return '{}({}:{})'.format(self.path, self.lineno, self.column)
def match(self, other) -> bool:
"""Returns true iff all the properties that appear in `self` have the
same value in `other`, but not necessarily vice versa.
"""
if not other or not isinstance(other, SourceLocation):
return False
if self.path and (self.path != other.path):
return False
if self.lineno and (self.lineno != other.lineno):
return False
if self.column and (self.column != other.column):
return False
return True
class StackFrame:
def __init__(self,
function: str = None,
is_inlined: bool = None,
location: SourceLocation = None,
watches: OrderedDict = None):
if watches is None:
watches = {}
self.function = function
self.is_inlined = is_inlined
self.location = location
self.watches = watches
def __str__(self):
return '{}{}: {} | {}'.format(
self.function,
' (inlined)' if self.is_inlined else '',
self.location,
{k: str(self.watches[k]) for k in self.watches})
def match(self, other) -> bool:
"""Returns true iff all the properties that appear in `self` have the
same value in `other`, but not necessarily vice versa.
"""
if not other or not isinstance(other, StackFrame):
return False
if self.location and not self.location.match(other.location):
return False
if self.watches:
for name in iter(self.watches):
try:
if isinstance(self.watches[name], dict):
for attr in iter(self.watches[name]):
if (getattr(other.watches[name], attr, None) !=
self.watches[name][attr]):
return False
else:
if other.watches[name].value != self.watches[name]:
return False
except KeyError:
return False
return True
class ProgramState:
def __init__(self, frames: List[StackFrame] = None):
self.frames = frames
def __str__(self):
return '\n'.join(map(
lambda enum: 'Frame {}: {}'.format(enum[0], enum[1]),
enumerate(self.frames)))
def match(self, other) -> bool:
"""Returns true iff all the properties that appear in `self` have the
same value in `other`, but not necessarily vice versa.
"""
if not other or not isinstance(other, ProgramState):
return False
if self.frames:
for idx, frame in enumerate(self.frames):
try:
if not frame.match(other.frames[idx]):
return False
except (IndexError, KeyError):
return False
return True

View File

@ -1,103 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Classes which are used to represent debugger steps."""
import json
from collections import OrderedDict
from typing import List
from enum import Enum
from dex.dextIR.FrameIR import FrameIR
from dex.dextIR.LocIR import LocIR
from dex.dextIR.ProgramState import ProgramState
class StopReason(Enum):
BREAKPOINT = 0
STEP = 1
PROGRAM_EXIT = 2
ERROR = 3
OTHER = 4
class StepKind(Enum):
FUNC = 0
FUNC_EXTERNAL = 1
FUNC_UNKNOWN = 2
VERTICAL_FORWARD = 3
SAME = 4
VERTICAL_BACKWARD = 5
UNKNOWN = 6
HORIZONTAL_FORWARD = 7
HORIZONTAL_BACKWARD = 8
class StepIR:
"""A debugger step.
Args:
watches (OrderedDict): { expression (str), result (ValueIR) }
"""
def __init__(self,
step_index: int,
stop_reason: StopReason,
frames: List[FrameIR],
step_kind: StepKind = None,
watches: OrderedDict = None,
program_state: ProgramState = None):
self.step_index = step_index
self.step_kind = step_kind
self.stop_reason = stop_reason
self.program_state = program_state
if frames is None:
frames = []
self.frames = frames
if watches is None:
watches = {}
self.watches = watches
def __str__(self):
try:
frame = self.current_frame
frame_info = (frame.function, frame.loc.path, frame.loc.lineno,
frame.loc.column)
except AttributeError:
frame_info = (None, None, None, None)
step_info = (self.step_index, ) + frame_info + (
str(self.stop_reason), str(self.step_kind),
[w for w in self.watches])
return '{}{}'.format('. ' * (self.num_frames - 1),
json.dumps(step_info))
@property
def num_frames(self):
return len(self.frames)
@property
def current_frame(self):
if not len(self.frames):
return None
return self.frames[0]
@property
def current_function(self):
try:
return self.current_frame.function
except AttributeError:
return None
@property
def current_location(self):
try:
return self.current_frame.loc
except AttributeError:
return LocIR(path=None, lineno=None, column=None)

View File

@ -1,38 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
class ValueIR:
"""Data class to store the result of an expression evaluation."""
def __init__(self,
expression: str,
value: str,
type_name: str,
could_evaluate: bool,
error_string: str = None,
is_optimized_away: bool = False,
is_irretrievable: bool = False):
self.expression = expression
self.value = value
self.type_name = type_name
self.could_evaluate = could_evaluate
self.error_string = error_string
self.is_optimized_away = is_optimized_away
self.is_irretrievable = is_irretrievable
def __str__(self):
prefix = '"{}": '.format(self.expression)
if self.error_string is not None:
return prefix + self.error_string
if self.value is not None:
return prefix + '({}) {}'.format(self.type_name, self.value)
return (prefix +
'could_evaluate: {}; irretrievable: {}; optimized_away: {};'
.format(self.could_evaluate, self.is_irretrievable,
self.is_optimized_away))

View File

@ -1,17 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""dextIR: DExTer Intermediate Representation of DExTer's debugger trace output.
"""
from dex.dextIR.BuilderIR import BuilderIR
from dex.dextIR.DextIR import DextIR
from dex.dextIR.DebuggerIR import DebuggerIR
from dex.dextIR.FrameIR import FrameIR
from dex.dextIR.LocIR import LocIR
from dex.dextIR.StepIR import StepIR, StepKind, StopReason
from dex.dextIR.ValueIR import ValueIR
from dex.dextIR.ProgramState import ProgramState, SourceLocation, StackFrame

View File

@ -1,497 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Calculate a 'score' based on some dextIR.
Assign penalties based on different commands to decrease the score.
1.000 would be a perfect score.
0.000 is the worst theoretical score possible.
"""
from collections import defaultdict, namedtuple, Counter
import difflib
import os
from itertools import groupby
from dex.command.StepValueInfo import StepValueInfo
PenaltyCommand = namedtuple('PenaltyCommand', ['pen_dict', 'max_penalty'])
# 'meta' field used in different ways by different things
PenaltyInstance = namedtuple('PenaltyInstance', ['meta', 'the_penalty'])
def add_heuristic_tool_arguments(parser):
parser.add_argument(
'--penalty-variable-optimized',
type=int,
default=3,
help='set the penalty multiplier for each'
' occurrence of a variable that was optimized'
' away',
metavar='<int>')
parser.add_argument(
'--penalty-misordered-values',
type=int,
default=3,
help='set the penalty multiplier for each'
' occurrence of a misordered value.',
metavar='<int>')
parser.add_argument(
'--penalty-irretrievable',
type=int,
default=4,
help='set the penalty multiplier for each'
" occurrence of a variable that couldn't"
' be retrieved',
metavar='<int>')
parser.add_argument(
'--penalty-not-evaluatable',
type=int,
default=5,
help='set the penalty multiplier for each'
" occurrence of a variable that couldn't"
' be evaluated',
metavar='<int>')
parser.add_argument(
'--penalty-missing-values',
type=int,
default=6,
help='set the penalty multiplier for each missing'
' value',
metavar='<int>')
parser.add_argument(
'--penalty-incorrect-values',
type=int,
default=7,
help='set the penalty multiplier for each'
' occurrence of an unexpected value.',
metavar='<int>')
parser.add_argument(
'--penalty-unreachable',
type=int,
default=4, # XXX XXX XXX selected by random
help='set the penalty for each line stepped onto that should'
' have been unreachable.',
metavar='<int>')
parser.add_argument(
'--penalty-misordered-steps',
type=int,
default=2, # XXX XXX XXX selected by random
help='set the penalty for differences in the order of steps'
' the program was expected to observe.',
metavar='<int>')
parser.add_argument(
'--penalty-missing-step',
type=int,
default=4, # XXX XXX XXX selected by random
help='set the penalty for the program skipping over a step.',
metavar='<int>')
parser.add_argument(
'--penalty-incorrect-program-state',
type=int,
default=4, # XXX XXX XXX selected by random
help='set the penalty for the program never entering an expected state'
' or entering an unexpected state.',
metavar='<int>')
class Heuristic(object):
def __init__(self, context, steps):
self.context = context
self.penalties = {}
worst_penalty = max([
self.penalty_variable_optimized, self.penalty_irretrievable,
self.penalty_not_evaluatable, self.penalty_incorrect_values,
self.penalty_missing_values, self.penalty_unreachable,
self.penalty_missing_step, self.penalty_misordered_steps
])
# Get DexExpectWatchType results.
try:
for command in steps.commands['DexExpectWatchType']:
command.eval(steps)
maximum_possible_penalty = min(3, len(
command.values)) * worst_penalty
name, p = self._calculate_expect_watch_penalties(
command, maximum_possible_penalty)
name = name + ' ExpectType'
self.penalties[name] = PenaltyCommand(p,
maximum_possible_penalty)
except KeyError:
pass
# Get DexExpectWatchValue results.
try:
for command in steps.commands['DexExpectWatchValue']:
command.eval(steps)
maximum_possible_penalty = min(3, len(
command.values)) * worst_penalty
name, p = self._calculate_expect_watch_penalties(
command, maximum_possible_penalty)
name = name + ' ExpectValue'
self.penalties[name] = PenaltyCommand(p,
maximum_possible_penalty)
except KeyError:
pass
try:
penalties = defaultdict(list)
maximum_possible_penalty_all = 0
for expect_state in steps.commands['DexExpectProgramState']:
success = expect_state.eval(steps)
p = 0 if success else self.penalty_incorrect_program_state
meta = 'expected {}: {}'.format(
'{} times'.format(expect_state.times)
if expect_state.times >= 0 else 'at least once',
expect_state.program_state_text)
if success:
meta = '<g>{}</>'.format(meta)
maximum_possible_penalty = self.penalty_incorrect_program_state
maximum_possible_penalty_all += maximum_possible_penalty
name = expect_state.program_state_text
penalties[meta] = [PenaltyInstance('{} times'.format(
len(expect_state.encounters)), p)]
self.penalties['expected program states'] = PenaltyCommand(
penalties, maximum_possible_penalty_all)
except KeyError:
pass
# Get the total number of each step kind.
step_kind_counts = defaultdict(int)
for step in getattr(steps, 'steps'):
step_kind_counts[step.step_kind] += 1
# Get DexExpectStepKind results.
penalties = defaultdict(list)
maximum_possible_penalty_all = 0
try:
for command in steps.commands['DexExpectStepKind']:
command.eval()
# Cap the penalty at 2 * expected count or else 1
maximum_possible_penalty = max(command.count * 2, 1)
p = abs(command.count - step_kind_counts[command.name])
actual_penalty = min(p, maximum_possible_penalty)
key = ('{}'.format(command.name)
if actual_penalty else '<g>{}</>'.format(command.name))
penalties[key] = [PenaltyInstance(p, actual_penalty)]
maximum_possible_penalty_all += maximum_possible_penalty
self.penalties['step kind differences'] = PenaltyCommand(
penalties, maximum_possible_penalty_all)
except KeyError:
pass
if 'DexUnreachable' in steps.commands:
cmds = steps.commands['DexUnreachable']
unreach_count = 0
# Find steps with unreachable in them
ureachs = [
s for s in steps.steps if 'DexUnreachable' in s.watches.keys()
]
# There's no need to match up cmds with the actual watches
upen = self.penalty_unreachable
count = upen * len(ureachs)
if count != 0:
d = dict()
for x in ureachs:
msg = 'line {} reached'.format(x.current_location.lineno)
d[msg] = [PenaltyInstance(upen, upen)]
else:
d = {
'<g>No unreachable lines seen</>': [PenaltyInstance(0, 0)]
}
total = PenaltyCommand(d, len(cmds) * upen)
self.penalties['unreachable lines'] = total
if 'DexExpectStepOrder' in steps.commands:
cmds = steps.commands['DexExpectStepOrder']
# Form a list of which line/cmd we _should_ have seen
cmd_num_lst = [(x, c.lineno) for c in cmds
for x in c.sequence]
# Order them by the sequence number
cmd_num_lst.sort(key=lambda t: t[0])
# Strip out sequence key
cmd_num_lst = [y for x, y in cmd_num_lst]
# Now do the same, but for the actually observed lines/cmds
ss = steps.steps
deso = [s for s in ss if 'DexExpectStepOrder' in s.watches.keys()]
deso = [s.watches['DexExpectStepOrder'] for s in deso]
# We rely on the steps remaining in order here
order_list = [int(x.expression) for x in deso]
# First off, check to see whether or not there are missing items
expected = Counter(cmd_num_lst)
seen = Counter(order_list)
unseen_line_dict = dict()
skipped_line_dict = dict()
mispen = self.penalty_missing_step
num_missing = 0
num_repeats = 0
for k, v in expected.items():
if k not in seen:
msg = 'Line {} not seen'.format(k)
unseen_line_dict[msg] = [PenaltyInstance(mispen, mispen)]
num_missing += v
elif v > seen[k]:
msg = 'Line {} skipped at least once'.format(k)
skipped_line_dict[msg] = [PenaltyInstance(mispen, mispen)]
num_missing += v - seen[k]
elif v < seen[k]:
# Don't penalise unexpected extra sightings of a line
# for now
num_repeats = seen[k] - v
pass
if len(unseen_line_dict) == 0:
pi = PenaltyInstance(0, 0)
unseen_line_dict['<g>All lines were seen</>'] = [pi]
if len(skipped_line_dict) == 0:
pi = PenaltyInstance(0, 0)
skipped_line_dict['<g>No lines were skipped</>'] = [pi]
total = PenaltyCommand(unseen_line_dict, len(expected) * mispen)
self.penalties['Unseen lines'] = total
total = PenaltyCommand(skipped_line_dict, len(expected) * mispen)
self.penalties['Skipped lines'] = total
ordpen = self.penalty_misordered_steps
cmd_num_lst = [str(x) for x in cmd_num_lst]
order_list = [str(x) for x in order_list]
lst = list(difflib.Differ().compare(cmd_num_lst, order_list))
diff_detail = Counter(l[0] for l in lst)
assert '?' not in diff_detail
# Diffs are hard to interpret; there are many algorithms for
# condensing them. Ignore all that, and just print out the changed
# sequences, it's up to the user to interpret what's going on.
def filt_lines(s, seg, e, key):
lst = [s]
for x in seg:
if x[0] == key:
lst.append(int(x[2:]))
lst.append(e)
return lst
diff_msgs = dict()
def reportdiff(start_idx, segment, end_idx):
msg = 'Order mismatch, expected linenos {}, saw {}'
expected_linenos = filt_lines(start_idx, segment, end_idx, '-')
seen_linenos = filt_lines(start_idx, segment, end_idx, '+')
msg = msg.format(expected_linenos, seen_linenos)
diff_msgs[msg] = [PenaltyInstance(ordpen, ordpen)]
# Group by changed segments.
start_expt_step = 0
end_expt_step = 0
to_print_lst = []
for k, subit in groupby(lst, lambda x: x[0] == ' '):
if k: # Whitespace group
nochanged = [x for x in subit]
end_expt_step = int(nochanged[0][2:])
if len(to_print_lst) > 0:
reportdiff(start_expt_step, to_print_lst,
end_expt_step)
start_expt_step = int(nochanged[-1][2:])
to_print_lst = []
else: # Diff group, save for printing
to_print_lst = [x for x in subit]
# If there was a dangling different step, print that too.
if len(to_print_lst) > 0:
reportdiff(start_expt_step, to_print_lst, '[End]')
if len(diff_msgs) == 0:
diff_msgs['<g>No lines misordered</>'] = [
PenaltyInstance(0, 0)
]
total = PenaltyCommand(diff_msgs, len(cmd_num_lst) * ordpen)
self.penalties['Misordered lines'] = total
return
def _calculate_expect_watch_penalties(self, c, maximum_possible_penalty):
penalties = defaultdict(list)
if c.line_range[0] == c.line_range[-1]:
line_range = str(c.line_range[0])
else:
line_range = '{}-{}'.format(c.line_range[0], c.line_range[-1])
name = '{}:{} [{}]'.format(
os.path.basename(c.path), line_range, c.expression)
num_actual_watches = len(c.expected_watches) + len(
c.unexpected_watches)
penalty_available = maximum_possible_penalty
# Only penalize for missing values if we have actually seen a watch
# that's returned us an actual value at some point, or if we've not
# encountered the value at all.
if num_actual_watches or c.times_encountered == 0:
for v in c.missing_values:
current_penalty = min(penalty_available,
self.penalty_missing_values)
penalty_available -= current_penalty
penalties['missing values'].append(
PenaltyInstance(v, current_penalty))
for v in c.encountered_values:
penalties['<g>expected encountered watches</>'].append(
PenaltyInstance(v, 0))
penalty_descriptions = [
(self.penalty_not_evaluatable, c.invalid_watches,
'could not evaluate'),
(self.penalty_variable_optimized, c.optimized_out_watches,
'result optimized away'),
(self.penalty_misordered_values, c.misordered_watches,
'misordered result'),
(self.penalty_irretrievable, c.irretrievable_watches,
'result could not be retrieved'),
(self.penalty_incorrect_values, c.unexpected_watches,
'unexpected result'),
]
for penalty_score, watches, description in penalty_descriptions:
# We only penalize the encountered issue for each missing value per
# command but we still want to record each one, so set the penalty
# to 0 after the threshold is passed.
times_to_penalize = len(c.missing_values)
for w in watches:
times_to_penalize -= 1
penalty_score = min(penalty_available, penalty_score)
penalty_available -= penalty_score
penalties[description].append(
PenaltyInstance(w, penalty_score))
if not times_to_penalize:
penalty_score = 0
return name, penalties
@property
def penalty(self):
result = 0
maximum_allowed_penalty = 0
for name, pen_cmd in self.penalties.items():
maximum_allowed_penalty += pen_cmd.max_penalty
value = pen_cmd.pen_dict
for category, inst_list in value.items():
result += sum(x.the_penalty for x in inst_list)
return min(result, maximum_allowed_penalty)
@property
def max_penalty(self):
return sum(p_cat.max_penalty for p_cat in self.penalties.values())
@property
def score(self):
try:
return 1.0 - (self.penalty / float(self.max_penalty))
except ZeroDivisionError:
return float('nan')
@property
def summary_string(self):
score = self.score
isnan = score != score # pylint: disable=comparison-with-itself
color = 'g'
if score < 0.25 or isnan:
color = 'r'
elif score < 0.75:
color = 'y'
return '<{}>({:.4f})</>'.format(color, score)
@property
def verbose_output(self): # noqa
string = ''
string += ('\n')
for command in sorted(self.penalties):
pen_cmd = self.penalties[command]
maximum_possible_penalty = pen_cmd.max_penalty
total_penalty = 0
lines = []
for category in sorted(pen_cmd.pen_dict):
lines.append(' <r>{}</>:\n'.format(category))
for result, penalty in pen_cmd.pen_dict[category]:
if isinstance(result, StepValueInfo):
text = 'step {}'.format(result.step_index)
if result.expected_value:
text += ' ({})'.format(result.expected_value)
else:
text = str(result)
if penalty:
assert penalty > 0, penalty
total_penalty += penalty
text += ' <r>[-{}]</>'.format(penalty)
lines.append(' {}\n'.format(text))
lines.append('\n')
string += (' <b>{}</> <y>[{}/{}]</>\n'.format(
command, total_penalty, maximum_possible_penalty))
for line in lines:
string += (line)
string += ('\n')
return string
@property
def penalty_variable_optimized(self):
return self.context.options.penalty_variable_optimized
@property
def penalty_irretrievable(self):
return self.context.options.penalty_irretrievable
@property
def penalty_not_evaluatable(self):
return self.context.options.penalty_not_evaluatable
@property
def penalty_incorrect_values(self):
return self.context.options.penalty_incorrect_values
@property
def penalty_missing_values(self):
return self.context.options.penalty_missing_values
@property
def penalty_misordered_values(self):
return self.context.options.penalty_misordered_values
@property
def penalty_unreachable(self):
return self.context.options.penalty_unreachable
@property
def penalty_missing_step(self):
return self.context.options.penalty_missing_step
@property
def penalty_misordered_steps(self):
return self.context.options.penalty_misordered_steps
@property
def penalty_incorrect_program_state(self):
return self.context.options.penalty_incorrect_program_state

View File

@ -1,8 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.heuristic.Heuristic import Heuristic, StepValueInfo

View File

@ -1,207 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""This is the main entry point.
It implements some functionality common to all subtools such as command line
parsing and running the unit-testing harnesses, before calling the reequested
subtool.
"""
import imp
import os
import sys
from dex.utils import PrettyOutput, Timer
from dex.utils import ExtArgParse as argparse
from dex.utils import get_root_directory
from dex.utils.Exceptions import Error, ToolArgumentError
from dex.utils.UnitTests import unit_tests_ok
from dex.utils.Version import version
from dex.utils import WorkingDirectory
from dex.utils.ReturnCode import ReturnCode
def _output_bug_report_message(context):
""" In the event of a catastrophic failure, print bug report request to the
user.
"""
context.o.red(
'\n\n'
'<g>****************************************</>\n'
'<b>****************************************</>\n'
'****************************************\n'
'** **\n'
'** <y>This is a bug in <a>DExTer</>.</> **\n'
'** **\n'
'** <y>Please report it.</> **\n'
'** **\n'
'****************************************\n'
'<b>****************************************</>\n'
'<g>****************************************</>\n'
'\n'
'<b>system:</>\n'
'<d>{}</>\n\n'
'<b>version:</>\n'
'<d>{}</>\n\n'
'<b>args:</>\n'
'<d>{}</>\n'
'\n'.format(sys.platform, version('DExTer'),
[sys.executable] + sys.argv),
stream=PrettyOutput.stderr)
def get_tools_directory():
""" Returns directory path where DExTer tool imports can be
found.
"""
tools_directory = os.path.join(get_root_directory(), 'tools')
assert os.path.isdir(tools_directory), tools_directory
return tools_directory
def get_tool_names():
""" Returns a list of expected DExTer Tools
"""
return [
'clang-opt-bisect', 'help', 'list-debuggers', 'no-tool-',
'run-debugger-internal-', 'test', 'view'
]
def _set_auto_highlights(context):
"""Flag some strings for auto-highlighting.
"""
context.o.auto_reds.extend([
r'[Ee]rror\:',
r'[Ee]xception\:',
r'un(expected|recognized) argument',
])
context.o.auto_yellows.extend([
r'[Ww]arning\:',
r'\(did you mean ',
r'During handling of the above exception, another exception',
])
def _get_options_and_args(context):
""" get the options and arguments from the commandline
"""
parser = argparse.ExtArgumentParser(context, add_help=False)
parser.add_argument('tool', default=None, nargs='?')
options, args = parser.parse_known_args(sys.argv[1:])
return options, args
def _get_tool_name(options):
""" get the name of the dexter tool (if passed) specified on the command
line, otherwise return 'no_tool_'.
"""
tool_name = options.tool
if tool_name is None:
tool_name = 'no_tool_'
else:
_is_valid_tool_name(tool_name)
return tool_name
def _is_valid_tool_name(tool_name):
""" check tool name matches a tool directory within the dexter tools
directory.
"""
valid_tools = get_tool_names()
if tool_name not in valid_tools:
raise Error('invalid tool "{}" (choose from {})'.format(
tool_name,
', '.join([t for t in valid_tools if not t.endswith('-')])))
def _import_tool_module(tool_name):
""" Imports the python module at the tool directory specificed by
tool_name.
"""
# format tool argument to reflect tool directory form.
tool_name = tool_name.replace('-', '_')
tools_directory = get_tools_directory()
module_info = imp.find_module(tool_name, [tools_directory])
return imp.load_module(tool_name, *module_info)
def tool_main(context, tool, args):
with Timer(tool.name):
options, defaults = tool.parse_command_line(args)
Timer.display = options.time_report
Timer.indent = options.indent_timer_level
Timer.fn = context.o.blue
context.options = options
context.version = version(tool.name)
if options.version:
context.o.green('{}\n'.format(context.version))
return ReturnCode.OK
if (options.unittest != 'off' and not unit_tests_ok(context)):
raise Error('<d>unit test failures</>')
if options.colortest:
context.o.colortest()
return ReturnCode.OK
try:
tool.handle_base_options(defaults)
except ToolArgumentError as e:
raise Error(e)
dir_ = context.options.working_directory
with WorkingDirectory(context, dir=dir_) as context.working_directory:
return_code = tool.go()
return return_code
class Context(object):
"""Context encapsulates globally useful objects and data; passed to many
Dexter functions.
"""
def __init__(self):
self.o: PrettyOutput = None
self.working_directory: str = None
self.options: dict = None
self.version: str = None
self.root_directory: str = None
def main() -> ReturnCode:
context = Context()
with PrettyOutput() as context.o:
try:
context.root_directory = get_root_directory()
# Flag some strings for auto-highlighting.
_set_auto_highlights(context)
options, args = _get_options_and_args(context)
# raises 'Error' if command line tool is invalid.
tool_name = _get_tool_name(options)
module = _import_tool_module(tool_name)
return tool_main(context, module.Tool(context), args)
except Error as e:
context.o.auto(
'\nerror: {}\n'.format(str(e)), stream=PrettyOutput.stderr)
try:
if context.options.error_debug:
raise
except AttributeError:
pass
return ReturnCode._ERROR
except (KeyboardInterrupt, SystemExit):
raise
except: # noqa
_output_bug_report_message(context)
raise

View File

@ -1,148 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Base class for subtools that do build/run tests."""
import abc
from datetime import datetime
import os
import sys
from dex.builder import add_builder_tool_arguments
from dex.builder import handle_builder_tool_options
from dex.debugger.Debuggers import add_debugger_tool_arguments
from dex.debugger.Debuggers import handle_debugger_tool_options
from dex.heuristic.Heuristic import add_heuristic_tool_arguments
from dex.tools.ToolBase import ToolBase
from dex.utils import get_root_directory, warn
from dex.utils.Exceptions import Error, ToolArgumentError
from dex.utils.ReturnCode import ReturnCode
class TestToolBase(ToolBase):
def __init__(self, *args, **kwargs):
super(TestToolBase, self).__init__(*args, **kwargs)
self.build_script: str = None
def add_tool_arguments(self, parser, defaults):
parser.description = self.__doc__
add_builder_tool_arguments(parser)
add_debugger_tool_arguments(parser, self.context, defaults)
add_heuristic_tool_arguments(parser)
parser.add_argument(
'test_path',
type=str,
metavar='<test-path>',
nargs='?',
default=os.path.abspath(
os.path.join(get_root_directory(), '..', 'tests')),
help='directory containing test(s)')
parser.add_argument(
'--results-directory',
type=str,
metavar='<directory>',
default=os.path.abspath(
os.path.join(get_root_directory(), '..', 'results',
datetime.now().strftime('%Y-%m-%d-%H%M-%S'))),
help='directory to save results')
def handle_options(self, defaults):
options = self.context.options
# We accept either or both of --binary and --builder.
if not options.binary and not options.builder:
raise Error('expected --builder or --binary')
# --binary overrides --builder
if options.binary:
if options.builder:
warn(self.context, "overriding --builder with --binary\n")
options.binary = os.path.abspath(options.binary)
if not os.path.isfile(options.binary):
raise Error('<d>could not find binary file</> <r>"{}"</>'
.format(options.binary))
else:
try:
self.build_script = handle_builder_tool_options(self.context)
except ToolArgumentError as e:
raise Error(e)
try:
handle_debugger_tool_options(self.context, defaults)
except ToolArgumentError as e:
raise Error(e)
options.test_path = os.path.abspath(options.test_path)
if not os.path.isfile(options.test_path) and not os.path.isdir(options.test_path):
raise Error(
'<d>could not find test path</> <r>"{}"</>'.format(
options.test_path))
options.results_directory = os.path.abspath(options.results_directory)
if not os.path.isdir(options.results_directory):
try:
os.makedirs(options.results_directory, exist_ok=True)
except OSError as e:
raise Error(
'<d>could not create directory</> <r>"{}"</> <y>({})</>'.
format(options.results_directory, e.strerror))
def go(self) -> ReturnCode: # noqa
options = self.context.options
options.executable = os.path.join(
self.context.working_directory.path, 'tmp.exe')
if os.path.isdir(options.test_path):
subdirs = sorted([
r for r, _, f in os.walk(options.test_path)
if 'test.cfg' in f
])
for subdir in subdirs:
# TODO: read file extensions from the test.cfg file instead so
# that this isn't just limited to C and C++.
options.source_files = [
os.path.normcase(os.path.join(subdir, f))
for f in os.listdir(subdir) if any(
f.endswith(ext) for ext in ['.c', '.cpp'])
]
self._run_test(self._get_test_name(subdir))
else:
options.source_files = [options.test_path]
self._run_test(self._get_test_name(options.test_path))
return self._handle_results()
@staticmethod
def _is_current_directory(test_directory):
return test_directory == '.'
def _get_test_name(self, test_path):
"""Get the test name from either the test file, or the sub directory
path it's stored in.
"""
# test names are distinguished by their relative path from the
# specified test path.
test_name = os.path.relpath(test_path,
self.context.options.test_path)
if self._is_current_directory(test_name):
test_name = os.path.basename(test_path)
return test_name
@abc.abstractmethod
def _run_test(self, test_dir):
pass
@abc.abstractmethod
def _handle_results(self) -> ReturnCode:
pass

View File

@ -1,135 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Base class for all subtools."""
import abc
import os
import tempfile
from dex import __version__
from dex.utils import ExtArgParse
from dex.utils import PrettyOutput
from dex.utils.ReturnCode import ReturnCode
class ToolBase(object, metaclass=abc.ABCMeta):
def __init__(self, context):
self.context = context
self.parser = None
@abc.abstractproperty
def name(self):
pass
@abc.abstractmethod
def add_tool_arguments(self, parser, defaults):
pass
def parse_command_line(self, args):
"""Define two parsers: pparser and self.parser.
pparser deals with args that need to be parsed prior to any of those of
self.parser. For example, any args which may affect the state of
argparse error output.
"""
class defaults(object):
pass
pparser = ExtArgParse.ExtArgumentParser(
self.context, add_help=False, prog=self.name)
pparser.add_argument(
'--no-color-output',
action='store_true',
default=False,
help='do not use colored output on stdout/stderr')
pparser.add_argument(
'--time-report',
action='store_true',
default=False,
help='display timing statistics')
self.parser = ExtArgParse.ExtArgumentParser(
self.context, parents=[pparser], prog=self.name)
self.parser.add_argument(
'-v',
'--verbose',
action='store_true',
default=False,
help='enable verbose output')
self.parser.add_argument(
'-V',
'--version',
action='store_true',
default=False,
help='display the DExTer version and exit')
self.parser.add_argument(
'-w',
'--no-warnings',
action='store_true',
default=False,
help='suppress warning output')
self.parser.add_argument(
'--unittest',
type=str,
choices=['off', 'show-failures', 'show-all'],
default='off',
help='run the DExTer codebase unit tests')
suppress = ExtArgParse.SUPPRESS # pylint: disable=no-member
self.parser.add_argument(
'--colortest', action='store_true', default=False, help=suppress)
self.parser.add_argument(
'--error-debug', action='store_true', default=False, help=suppress)
defaults.working_directory = os.path.join(tempfile.gettempdir(),
'dexter')
self.parser.add_argument(
'--indent-timer-level', type=int, default=1, help=suppress)
self.parser.add_argument(
'--working-directory',
type=str,
metavar='<file>',
default=None,
display_default=defaults.working_directory,
help='location of working directory')
self.parser.add_argument(
'--save-temps',
action='store_true',
default=False,
help='save temporary files')
self.add_tool_arguments(self.parser, defaults)
# If an error is encountered during pparser, show the full usage text
# including self.parser options. Strip the preceding 'usage: ' to avoid
# having it appear twice.
pparser.usage = self.parser.format_usage().lstrip('usage: ')
options, args = pparser.parse_known_args(args)
if options.no_color_output:
PrettyOutput.stdout.color_enabled = False
PrettyOutput.stderr.color_enabled = False
options = self.parser.parse_args(args, namespace=options)
return options, defaults
def handle_base_options(self, defaults):
self.handle_options(defaults)
options = self.context.options
if options.working_directory is None:
options.working_directory = defaults.working_directory
@abc.abstractmethod
def handle_options(self, defaults):
pass
@abc.abstractmethod
def go(self) -> ReturnCode:
pass

View File

@ -1,10 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.tools.Main import Context, get_tool_names, get_tools_directory, main, tool_main
from dex.tools.TestToolBase import TestToolBase
from dex.tools.ToolBase import ToolBase

View File

@ -1,286 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Clang opt-bisect tool."""
from collections import defaultdict
import os
import csv
import re
import pickle
from dex.builder import run_external_build_script
from dex.debugger.Debuggers import empty_debugger_steps, get_debugger_steps
from dex.heuristic import Heuristic
from dex.tools import TestToolBase
from dex.utils.Exceptions import DebuggerException, Error
from dex.utils.Exceptions import BuildScriptException, HeuristicException
from dex.utils.PrettyOutputBase import Stream
from dex.utils.ReturnCode import ReturnCode
class BisectPass(object):
def __init__(self, no, description, description_no_loc):
self.no = no
self.description = description
self.description_no_loc = description_no_loc
self.penalty = 0
self.differences = []
class Tool(TestToolBase):
"""Use the LLVM "-opt-bisect-limit=<n>" flag to get information on the
contribution of each LLVM pass to the overall DExTer score when using
clang.
Clang is run multiple times, with an increasing value of n, measuring the
debugging experience at each value.
"""
_re_running_pass = re.compile(
r'^BISECT\: running pass \((\d+)\) (.+?)( \(.+\))?$')
def __init__(self, *args, **kwargs):
super(Tool, self).__init__(*args, **kwargs)
self._all_bisect_pass_summary = defaultdict(list)
@property
def name(self):
return 'DExTer clang opt bisect'
def _get_bisect_limits(self):
options = self.context.options
max_limit = 999999
limits = [max_limit for _ in options.source_files]
all_passes = [
l for l in self._clang_opt_bisect_build(limits)[1].splitlines()
if l.startswith('BISECT: running pass (')
]
results = []
for i, pass_ in enumerate(all_passes[1:]):
if pass_.startswith('BISECT: running pass (1)'):
results.append(all_passes[i])
results.append(all_passes[-1])
assert len(results) == len(
options.source_files), (results, options.source_files)
limits = [
int(Tool._re_running_pass.match(r).group(1)) for r in results
]
return limits
def _run_test(self, test_name): # noqa
options = self.context.options
per_pass_score = []
current_bisect_pass_summary = defaultdict(list)
max_limits = self._get_bisect_limits()
overall_limit = sum(max_limits)
prev_score = 1.0
prev_steps_str = None
for current_limit in range(overall_limit + 1):
# Take the overall limit number and split it across buckets for
# each source file.
limit_remaining = current_limit
file_limits = [0] * len(max_limits)
for i, max_limit in enumerate(max_limits):
if limit_remaining < max_limit:
file_limits[i] += limit_remaining
break
else:
file_limits[i] = max_limit
limit_remaining -= file_limits[i]
f = [l for l in file_limits if l]
current_file_index = len(f) - 1 if f else 0
_, err, builderIR = self._clang_opt_bisect_build(file_limits)
err_lines = err.splitlines()
# Find the last line that specified a running pass.
for l in err_lines[::-1]:
match = Tool._re_running_pass.match(l)
if match:
pass_info = match.groups()
break
else:
pass_info = (0, None, None)
try:
steps = get_debugger_steps(self.context)
except DebuggerException:
steps = empty_debugger_steps(self.context)
steps.builder = builderIR
try:
heuristic = Heuristic(self.context, steps)
except HeuristicException as e:
raise Error(e)
score_difference = heuristic.score - prev_score
prev_score = heuristic.score
isnan = heuristic.score != heuristic.score
if isnan or score_difference < 0:
color1 = 'r'
color2 = 'r'
elif score_difference > 0:
color1 = 'g'
color2 = 'g'
else:
color1 = 'y'
color2 = 'd'
summary = '<{}>running pass {}/{} on "{}"'.format(
color2, pass_info[0], max_limits[current_file_index],
test_name)
if len(options.source_files) > 1:
summary += ' [{}/{}]'.format(current_limit, overall_limit)
pass_text = ''.join(p for p in pass_info[1:] if p)
summary += ': {} <{}>{:+.4f}</> <{}>{}</></>\n'.format(
heuristic.summary_string, color1, score_difference, color2,
pass_text)
self.context.o.auto(summary)
heuristic_verbose_output = heuristic.verbose_output
if options.verbose:
self.context.o.auto(heuristic_verbose_output)
steps_str = str(steps)
steps_changed = steps_str != prev_steps_str
prev_steps_str = steps_str
# If this is the first pass, or something has changed, write a text
# file containing verbose information on the current status.
if current_limit == 0 or score_difference or steps_changed:
file_name = '-'.join(
str(s) for s in [
'status', test_name, '{{:0>{}}}'.format(
len(str(overall_limit))).format(current_limit),
'{:.4f}'.format(heuristic.score).replace(
'.', '_'), pass_info[1]
] if s is not None)
file_name = ''.join(
c for c in file_name
if c.isalnum() or c in '()-_./ ').strip().replace(
' ', '_').replace('/', '_')
output_text_path = os.path.join(options.results_directory,
'{}.txt'.format(file_name))
with open(output_text_path, 'w') as fp:
self.context.o.auto(summary + '\n', stream=Stream(fp))
self.context.o.auto(str(steps) + '\n', stream=Stream(fp))
self.context.o.auto(
heuristic_verbose_output + '\n', stream=Stream(fp))
output_dextIR_path = os.path.join(options.results_directory,
'{}.dextIR'.format(file_name))
with open(output_dextIR_path, 'wb') as fp:
pickle.dump(steps, fp, protocol=pickle.HIGHEST_PROTOCOL)
per_pass_score.append((test_name, pass_text,
heuristic.score))
if pass_info[1]:
self._all_bisect_pass_summary[pass_info[1]].append(
score_difference)
current_bisect_pass_summary[pass_info[1]].append(
score_difference)
per_pass_score_path = os.path.join(
options.results_directory,
'{}-per_pass_score.csv'.format(test_name))
with open(per_pass_score_path, mode='w', newline='') as fp:
writer = csv.writer(fp, delimiter=',')
writer.writerow(['Source File', 'Pass', 'Score'])
for path, pass_, score in per_pass_score:
writer.writerow([path, pass_, score])
self.context.o.blue('wrote "{}"\n'.format(per_pass_score_path))
pass_summary_path = os.path.join(
options.results_directory, '{}-pass-summary.csv'.format(test_name))
self._write_pass_summary(pass_summary_path,
current_bisect_pass_summary)
def _handle_results(self) -> ReturnCode:
options = self.context.options
pass_summary_path = os.path.join(options.results_directory,
'overall-pass-summary.csv')
self._write_pass_summary(pass_summary_path,
self._all_bisect_pass_summary)
return ReturnCode.OK
def _clang_opt_bisect_build(self, opt_bisect_limits):
options = self.context.options
compiler_options = [
'{} -mllvm -opt-bisect-limit={}'.format(options.cflags,
opt_bisect_limit)
for opt_bisect_limit in opt_bisect_limits
]
linker_options = options.ldflags
try:
return run_external_build_script(
self.context,
source_files=options.source_files,
compiler_options=compiler_options,
linker_options=linker_options,
script_path=self.build_script,
executable_file=options.executable)
except BuildScriptException as e:
raise Error(e)
def _write_pass_summary(self, path, pass_summary):
# Get a list of tuples.
pass_summary_list = list(pass_summary.items())
for i, item in enumerate(pass_summary_list):
# Add elems for the sum, min, and max of the values, as well as
# 'interestingness' which is whether any of these values are
# non-zero.
pass_summary_list[i] += (sum(item[1]), min(item[1]), max(item[1]),
any(item[1]))
# Split the pass name into the basic name and kind.
pass_summary_list[i] += tuple(item[0].rsplit(' on ', 1))
# Sort the list by the following columns in order of precedence:
# - Is interesting (True first)
# - Sum (smallest first)
# - Number of times pass ran (largest first)
# - Kind (alphabetically)
# - Name (alphabetically)
pass_summary_list.sort(
key=lambda tup: (not tup[5], tup[2], -len(tup[1]), tup[7], tup[6]))
with open(path, mode='w', newline='') as fp:
writer = csv.writer(fp, delimiter=',')
writer.writerow(
['Pass', 'Kind', 'Sum', 'Min', 'Max', 'Interesting'])
for (_, vals, sum_, min_, max_, interesting, name,
kind) in pass_summary_list:
writer.writerow([name, kind, sum_, min_, max_, interesting] +
vals)
self.context.o.blue('wrote "{}"\n'.format(path))

View File

@ -1,8 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.tools.clang_opt_bisect.Tool import Tool

View File

@ -1,61 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Help tool."""
import imp
import textwrap
from dex.tools import ToolBase, get_tool_names, get_tools_directory, tool_main
from dex.utils.ReturnCode import ReturnCode
class Tool(ToolBase):
"""Provides help info on subtools."""
@property
def name(self):
return 'DExTer help'
@property
def _visible_tool_names(self):
return [t for t in get_tool_names() if not t.endswith('-')]
def add_tool_arguments(self, parser, defaults):
parser.description = Tool.__doc__
parser.add_argument(
'tool',
choices=self._visible_tool_names,
nargs='?',
help='name of subtool')
def handle_options(self, defaults):
pass
@property
def _default_text(self):
s = '\n<b>The following subtools are available:</>\n\n'
tools_directory = get_tools_directory()
for tool_name in sorted(self._visible_tool_names):
internal_name = tool_name.replace('-', '_')
module_info = imp.find_module(internal_name, [tools_directory])
tool_doc = imp.load_module(internal_name,
*module_info).Tool.__doc__
tool_doc = tool_doc.strip() if tool_doc else ''
tool_doc = textwrap.fill(' '.join(tool_doc.split()), 80)
s += '<g>{}</>\n{}\n\n'.format(tool_name, tool_doc)
return s
def go(self) -> ReturnCode:
if self.context.options.tool is None:
self.context.o.auto(self._default_text)
return ReturnCode.OK
tool_name = self.context.options.tool.replace('-', '_')
tools_directory = get_tools_directory()
module_info = imp.find_module(tool_name, [tools_directory])
module = imp.load_module(tool_name, *module_info)
return tool_main(self.context, module.Tool(self.context), ['--help'])

View File

@ -1,8 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.tools.help.Tool import Tool

View File

@ -1,40 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""List debuggers tool."""
from dex.debugger.Debuggers import add_debugger_tool_base_arguments
from dex.debugger.Debuggers import handle_debugger_tool_base_options
from dex.debugger.Debuggers import Debuggers
from dex.tools import ToolBase
from dex.utils import Timer
from dex.utils.Exceptions import DebuggerException, Error
from dex.utils.ReturnCode import ReturnCode
class Tool(ToolBase):
"""List all of the potential debuggers that DExTer knows about and whether
there is currently a valid interface available for them.
"""
@property
def name(self):
return 'DExTer list debuggers'
def add_tool_arguments(self, parser, defaults):
parser.description = Tool.__doc__
add_debugger_tool_base_arguments(parser, defaults)
def handle_options(self, defaults):
handle_debugger_tool_base_options(self.context, defaults)
def go(self) -> ReturnCode:
with Timer('list debuggers'):
try:
Debuggers(self.context).list()
except DebuggerException as e:
raise Error(e)
return ReturnCode.OK

View File

@ -1,8 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.tools.list_debuggers.Tool import Tool

View File

@ -1,49 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""This is a special subtool that is run when no subtool is specified.
It just provides a welcome message and simple usage instructions.
"""
from dex.tools import ToolBase, get_tool_names
from dex.utils.Exceptions import Error
from dex.utils.ReturnCode import ReturnCode
# This is a special "tool" that is run when no subtool has been specified on
# the command line. Its only job is to provide useful usage info.
class Tool(ToolBase):
"""Welcome to DExTer (Debugging Experience Tester).
Please choose a subtool from the list below. Use 'dexter.py help' for more
information.
"""
@property
def name(self):
return 'DExTer'
def add_tool_arguments(self, parser, defaults):
parser.description = Tool.__doc__
parser.add_argument(
'subtool',
choices=[t for t in get_tool_names() if not t.endswith('-')],
nargs='?',
help='name of subtool')
parser.add_argument(
'subtool_options',
metavar='subtool-options',
nargs='*',
help='subtool specific options')
def handle_options(self, defaults):
if not self.context.options.subtool:
raise Error('<d>no subtool specified</>\n\n{}\n'.format(
self.parser.format_help()))
def go(self) -> ReturnCode:
# This fn is never called because not specifying a subtool raises an
# exception.
return ReturnCode._ERROR

View File

@ -1,8 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.tools.no_tool_.Tool import Tool

View File

@ -1,74 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""This is an internal subtool used to sandbox the communication with a
debugger into a separate process so that any crashes inside the debugger will
not bring down the entire DExTer tool.
"""
import pickle
from dex.debugger import Debuggers
from dex.tools import ToolBase
from dex.utils import Timer
from dex.utils.Exceptions import DebuggerException, Error
from dex.utils.ReturnCode import ReturnCode
class Tool(ToolBase):
def __init__(self, *args, **kwargs):
super(Tool, self).__init__(*args, **kwargs)
self.dextIR = None
@property
def name(self):
return 'DExTer run debugger internal'
def add_tool_arguments(self, parser, defaults):
parser.add_argument('dextIR_path', type=str, help='dextIR file')
parser.add_argument(
'pickled_options', type=str, help='pickled options file')
def handle_options(self, defaults):
with open(self.context.options.dextIR_path, 'rb') as fp:
self.dextIR = pickle.load(fp)
with open(self.context.options.pickled_options, 'rb') as fp:
poptions = pickle.load(fp)
poptions.working_directory = (
self.context.options.working_directory[:])
poptions.unittest = self.context.options.unittest
poptions.dextIR_path = self.context.options.dextIR_path
self.context.options = poptions
Timer.display = self.context.options.time_report
def go(self) -> ReturnCode:
options = self.context.options
with Timer('loading debugger'):
debugger = Debuggers(self.context).load(options.debugger,
self.dextIR)
self.dextIR.debugger = debugger.debugger_info
with Timer('running debugger'):
if not debugger.is_available:
msg = '<d>could not load {}</> ({})\n'.format(
debugger.name, debugger.loading_error)
if options.verbose:
msg = '{}\n {}'.format(
msg, ' '.join(debugger.loading_error_trace))
raise Error(msg)
with debugger:
try:
debugger.start()
except DebuggerException as e:
raise Error(e)
with open(self.context.options.dextIR_path, 'wb') as fp:
pickle.dump(self.dextIR, fp)
return ReturnCode.OK

View File

@ -1,8 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.tools.run_debugger_internal_.Tool import Tool

View File

@ -1,244 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Test tool."""
import os
import csv
import pickle
import shutil
from dex.builder import run_external_build_script
from dex.debugger.Debuggers import get_debugger_steps
from dex.heuristic import Heuristic
from dex.tools import TestToolBase
from dex.utils.Exceptions import DebuggerException
from dex.utils.Exceptions import BuildScriptException, HeuristicException
from dex.utils.PrettyOutputBase import Stream
from dex.utils.ReturnCode import ReturnCode
from dex.dextIR import BuilderIR
class TestCase(object):
def __init__(self, context, name, heuristic, error):
self.context = context
self.name = name
self.heuristic = heuristic
self.error = error
@property
def penalty(self):
try:
return self.heuristic.penalty
except AttributeError:
return float('nan')
@property
def max_penalty(self):
try:
return self.heuristic.max_penalty
except AttributeError:
return float('nan')
@property
def score(self):
try:
return self.heuristic.score
except AttributeError:
return float('nan')
def __str__(self):
if self.error and self.context.options.verbose:
verbose_error = str(self.error)
else:
verbose_error = ''
if self.error:
script_error = (' : {}'.format(
self.error.script_error.splitlines()[0].decode()) if getattr(
self.error, 'script_error', None) else '')
error = ' [{}{}]'.format(
str(self.error).splitlines()[0], script_error)
else:
error = ''
try:
summary = self.heuristic.summary_string
except AttributeError:
summary = '<r>nan/nan (nan)</>'
return '{}: {}{}\n{}'.format(self.name, summary, error, verbose_error)
class Tool(TestToolBase):
"""Run the specified DExTer test(s) with the specified compiler and linker
options and produce a dextIR file as well as printing out the debugging
experience score calculated by the DExTer heuristic.
"""
def __init__(self, *args, **kwargs):
super(Tool, self).__init__(*args, **kwargs)
self._test_cases = []
@property
def name(self):
return 'DExTer test'
def add_tool_arguments(self, parser, defaults):
parser.add_argument('--fail-lt',
type=float,
default=0.0, # By default TEST always succeeds.
help='exit with status FAIL(2) if the test result'
' is less than this value.',
metavar='<float>')
super(Tool, self).add_tool_arguments(parser, defaults)
def _build_test_case(self):
"""Build an executable from the test source with the given --builder
script and flags (--cflags, --ldflags) in the working directory.
Or, if the --binary option has been given, copy the executable provided
into the working directory and rename it to match the --builder output.
"""
options = self.context.options
if options.binary:
# Copy user's binary into the tmp working directory
shutil.copy(options.binary, options.executable)
builderIR = BuilderIR(
name='binary',
cflags=[options.binary],
ldflags='')
else:
options = self.context.options
compiler_options = [options.cflags for _ in options.source_files]
linker_options = options.ldflags
_, _, builderIR = run_external_build_script(
self.context,
script_path=self.build_script,
source_files=options.source_files,
compiler_options=compiler_options,
linker_options=linker_options,
executable_file=options.executable)
return builderIR
def _get_steps(self, builderIR):
"""Generate a list of debugger steps from a test case.
"""
steps = get_debugger_steps(self.context)
steps.builder = builderIR
return steps
def _get_results_basename(self, test_name):
def splitall(x):
while len(x) > 0:
x, y = os.path.split(x)
yield y
all_components = reversed([x for x in splitall(test_name)])
return '_'.join(all_components)
def _get_results_path(self, test_name):
"""Returns the path to the test results directory for the test denoted
by test_name.
"""
return os.path.join(self.context.options.results_directory,
self._get_results_basename(test_name))
def _get_results_text_path(self, test_name):
"""Returns path results .txt file for test denoted by test_name.
"""
test_results_path = self._get_results_path(test_name)
return '{}.txt'.format(test_results_path)
def _get_results_pickle_path(self, test_name):
"""Returns path results .dextIR file for test denoted by test_name.
"""
test_results_path = self._get_results_path(test_name)
return '{}.dextIR'.format(test_results_path)
def _record_steps(self, test_name, steps):
"""Write out the set of steps out to the test's .txt and .json
results file.
"""
output_text_path = self._get_results_text_path(test_name)
with open(output_text_path, 'w') as fp:
self.context.o.auto(str(steps), stream=Stream(fp))
output_dextIR_path = self._get_results_pickle_path(test_name)
with open(output_dextIR_path, 'wb') as fp:
pickle.dump(steps, fp, protocol=pickle.HIGHEST_PROTOCOL)
def _record_score(self, test_name, heuristic):
"""Write out the test's heuristic score to the results .txt file.
"""
output_text_path = self._get_results_text_path(test_name)
with open(output_text_path, 'a') as fp:
self.context.o.auto(heuristic.verbose_output, stream=Stream(fp))
def _record_test_and_display(self, test_case):
"""Output test case to o stream and record test case internally for
handling later.
"""
self.context.o.auto(test_case)
self._test_cases.append(test_case)
def _record_failed_test(self, test_name, exception):
"""Instantiate a failed test case with failure exception and
store internally.
"""
test_case = TestCase(self.context, test_name, None, exception)
self._record_test_and_display(test_case)
def _record_successful_test(self, test_name, steps, heuristic):
"""Instantiate a successful test run, store test for handling later.
Display verbose output for test case if required.
"""
test_case = TestCase(self.context, test_name, heuristic, None)
self._record_test_and_display(test_case)
if self.context.options.verbose:
self.context.o.auto('\n{}\n'.format(steps))
self.context.o.auto(heuristic.verbose_output)
def _run_test(self, test_name):
"""Attempt to run test files specified in options.source_files. Store
result internally in self._test_cases.
"""
try:
builderIR = self._build_test_case()
steps = self._get_steps(builderIR)
self._record_steps(test_name, steps)
heuristic_score = Heuristic(self.context, steps)
self._record_score(test_name, heuristic_score)
except (BuildScriptException, DebuggerException,
HeuristicException) as e:
self._record_failed_test(test_name, e)
return
self._record_successful_test(test_name, steps, heuristic_score)
return
def _handle_results(self) -> ReturnCode:
return_code = ReturnCode.OK
options = self.context.options
if not options.verbose:
self.context.o.auto('\n')
summary_path = os.path.join(options.results_directory, 'summary.csv')
with open(summary_path, mode='w', newline='') as fp:
writer = csv.writer(fp, delimiter=',')
writer.writerow(['Test Case', 'Score', 'Error'])
for test_case in self._test_cases:
if (test_case.score < options.fail_lt or
test_case.error is not None):
return_code = ReturnCode.FAIL
writer.writerow([
test_case.name, '{:.4f}'.format(test_case.score),
test_case.error
])
return return_code

View File

@ -1,8 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.tools.test.Tool import Tool

View File

@ -1,59 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""View tool."""
import os
import pickle
from dex.heuristic import Heuristic
from dex.heuristic.Heuristic import add_heuristic_tool_arguments
from dex.tools import ToolBase
from dex.utils.Exceptions import Error, HeuristicException
from dex.utils.ReturnCode import ReturnCode
class Tool(ToolBase):
"""Given a dextIR file, display the information in a human-readable form.
"""
@property
def name(self):
return 'DExTer view'
def add_tool_arguments(self, parser, defaults):
add_heuristic_tool_arguments(parser)
parser.add_argument(
'input_path',
metavar='dextIR-file',
type=str,
default=None,
help='dexter dextIR file to view')
parser.description = Tool.__doc__
def handle_options(self, defaults):
options = self.context.options
options.input_path = os.path.abspath(options.input_path)
if not os.path.isfile(options.input_path):
raise Error('<d>could not find dextIR file</> <r>"{}"</>'.format(
options.input_path))
def go(self) -> ReturnCode:
options = self.context.options
with open(options.input_path, 'rb') as fp:
steps = pickle.load(fp)
try:
heuristic = Heuristic(self.context, steps)
except HeuristicException as e:
raise Error('could not apply heuristic: {}'.format(e))
self.context.o.auto('{}\n\n{}\n\n{}\n\n'.format(
heuristic.summary_string, steps, heuristic.verbose_output))
return ReturnCode.OK

View File

@ -1,8 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from dex.tools.view.Tool import Tool

View File

@ -1,22 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Utility functions for querying the current environment."""
import os
def is_native_windows():
return os.name == 'nt'
def has_pywin32():
try:
import win32com.client # pylint:disable=unused-variable
import win32api # pylint:disable=unused-variable
return True
except ImportError:
return False

View File

@ -1,72 +0,0 @@
# DExTer : Debugging Experience Tester
# ~~~~~~ ~ ~~ ~ ~~
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Provides Dexter-specific exception types."""
class Dexception(Exception):
"""All dexter-specific exceptions derive from this."""
pass
class Error(Dexception):
"""Error. Prints 'error: <message>' without a traceback."""
pass
class DebuggerException(Dexception):
"""Any error from using the debugger."""
def __init__(self, msg, orig_exception=None):
super(DebuggerException, self).__init__(msg)
self.msg = msg
self.orig_exception = orig_exception
def __str__(self):
return str(self.msg)
class LoadDebuggerException(DebuggerException):
"""If specified debugger cannot be loaded."""
pass
class NotYetLoadedDebuggerException(LoadDebuggerException):
"""If specified debugger has not yet been attempted to load."""
def __init__(self):
super(NotYetLoadedDebuggerException,
self).__init__('not loaded', orig_exception=None)
class CommandParseError(Dexception):
"""If a command instruction cannot be successfully parsed."""
def __init__(self, *args, **kwargs):
super(CommandParseError, self).__init__(*args, **kwargs)
self.filename = None
self.lineno = None
self.info = None
self.src = None
self.caret = None
class ToolArgumentError(Dexception):
"""If a tool argument is invalid."""
pass
class BuildScriptException(Dexception):
"""If there is an error in a build script file."""
def __init__(self, *args, **kwargs):
self.script_error = kwargs.pop('script_error', None)
super(BuildScriptException, self).__init__(*args, **kwargs)
class HeuristicException(Dexception):
"""If there was a problem with the heuristic."""
pass

Some files were not shown because too many files have changed in this diff Show More