Skip to content

Commit bc1794c

Browse files
committed
implement C_BP macro for throwing a C debugger breakpoint WIP
-short macro name, less to type -cross platform -makes it easier to work on Perl core or XS CPAN -emits debug info to console for CI/smoke/unattended machine -writes to STDOUT and STDERR, incase one of the 2 FDs are redirected to a disk file or piped to another process, or that disk file is temp flagged, and OS instantly deletes it -breaking TAP testing is good -C_BP; is less to type vs DebugBreak(); or __debugbreak(); on Win32
1 parent 82c4939 commit bc1794c

File tree

7 files changed

+286
-1
lines changed

7 files changed

+286
-1
lines changed

embed.fnc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,7 @@ CRTp |I32 |cast_i32 |NV f
802802
CRTp |IV |cast_iv |NV f
803803
CRTp |U32 |cast_ulong |NV f
804804
CRTp |UV |cast_uv |NV f
805+
TXdp |void |c_bp |NN const char *file_metadata
805806
p |bool |check_utf8_print \
806807
|NN const U8 *s \
807808
|const STRLEN len

embed.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,7 @@
917917
# define boot_core_builtin() Perl_boot_core_builtin(aTHX)
918918
# define boot_core_mro() Perl_boot_core_mro(aTHX)
919919
# define build_infix_plugin(a,b,c) Perl_build_infix_plugin(aTHX_ a,b,c)
920+
# define c_bp Perl_c_bp
920921
# define cando(a,b,c) Perl_cando(aTHX_ a,b,c)
921922
# define check_utf8_print(a,b) Perl_check_utf8_print(aTHX_ a,b)
922923
# define closest_cop(a,b,c,d) Perl_closest_cop(aTHX_ a,b,c,d)

ext/XS-APItest/APItest.xs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3152,6 +3152,20 @@ my_cxt_setsv(sv)
31523152
my_cxt_setsv_p(sv _aMY_CXT);
31533153
SvREFCNT_inc(sv);
31543154

3155+
void
3156+
test_C_BP_breakpoint()
3157+
CODE:
3158+
{
3159+
#ifdef WIN32
3160+
UINT em = GetErrorMode();
3161+
SetErrorMode( SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX );
3162+
#endif
3163+
C_BP;
3164+
#ifdef WIN32
3165+
SetErrorMode(em);
3166+
#endif
3167+
}
3168+
31553169
bool
31563170
sv_setsv_cow_hashkey_core()
31573171

proto.h

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

t/uni/caller.t

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ BEGIN {
77
set_up_inc('../lib');
88
}
99

10+
use Config;
1011
use utf8;
1112
use open qw( :utf8 :std );
1213

13-
plan( tests => 18 );
14+
plan( tests => 19 );
1415

1516
package main;
1617

@@ -74,3 +75,30 @@ $^P = 16;
7475
$^P = $saved_perldb;
7576

7677
::is( eval 'pb()', 'main::pb', 'actually return the right function name even if $^P had been on at some point' );
78+
79+
# Skip the OS signal/exception from this faux-SEGV
80+
# code is from cpan/Test-Harness/t/harness.t
81+
SKIP: {
82+
::skip "No SIGSEGV on $^O", 1
83+
if $^O ne 'MSWin32' && $Config::Config{'sig_name'} !~ m/SEGV/;
84+
#line below not in cpan/Test-Harness/t/harness.t
85+
::skip "No SIGTRAP on $^O", 1
86+
if $^O ne 'MSWin32' && $Config::Config{'sig_name'} !~ m/TRAP/;
87+
88+
# some people -Dcc="somecc -fsanitize=..." or -Doptimize="-fsanitize=..."
89+
::skip "ASAN doesn't passthrough SEGV", 1
90+
if "$Config{cc} $Config{ccflags} $Config{optimize}" =~ /-fsanitize\b/;
91+
92+
my $out_str = ::fresh_perl('use XS::APItest; XS::APItest::test_C_BP_breakpoint();');
93+
94+
# On machines where 'ulimit -c' does not return '0', a perl.core
95+
# file is created here. We don't need to examine it, and it's
96+
# annoying to have it subsequently show up as an untracked file in
97+
# `git status`, so simply get rid of it per suggestion by Karen
98+
# Etheridge.
99+
END { unlink 'perl.core' }
100+
101+
102+
::like($out_str, qr/panic: C breakpoint hit file/,
103+
'C_BP macro and C breakpoint works');
104+
}

util.c

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2003,6 +2003,148 @@ Perl_croak_popstack(void)
20032003
my_exit(1);
20042004
}
20052005

2006+
/*
2007+
=for apidoc c_bp
2008+
2009+
Internal helper for C<C_BP>. Not to be called directly.
2010+
2011+
Prints file name, C function name, line number, and CPU the instruction
2012+
pointer. Instruction pointer intended to be copied to a C debugger tool or
2013+
disassembler or used with core dumps. It is a faux-function pointer to
2014+
somewhere in the middle of the caller's C function, this address can never
2015+
be casted from I<void *> to a function pointer, then called, a SEGV will
2016+
occur.
2017+
2018+
=cut
2019+
*/
2020+
2021+
void
2022+
Perl_c_bp(const char * file_metadata)
2023+
{
2024+
/* file_metadata is a string in the format of "XS_my_func*XSModule.c*6789"
2025+
The 3 arguments are catted together by CPP, so in the caller,
2026+
when using a C debugger, you press "Step One" key 2 times less, when
2027+
using step by disassembly view. C_BP macro should never appear in
2028+
public Stable/Gold releases of Perl core or any CPAN module. Using
2029+
C_BP even in a alpha release, is questionable. Smokers/CI greatly
2030+
dislike SEGVs which someone require human intervention to unfreeze
2031+
the console or unattended CI tool.
2032+
*/
2033+
2034+
/* XXX improvements, identify which .so/.dll on disk this address is from.
2035+
Ajust value to a 0-indexed value to remove ASLR randomizing between
2036+
process runs. Better integration with USE_C_BACKTRACE if
2037+
USE_C_BACKTRACE enabled on a particular platform. */
2038+
#if defined(__has_builtin) && __has_builtin(__builtin_return_address)
2039+
void * ip = __builtin_return_address(0); /* GCC family */
2040+
#elif _MSC_VER
2041+
void * ip = _ReturnAddress();
2042+
#else
2043+
/* last resort, seems to work on all CPU archs, guaranteed to work
2044+
on all x86/x64 OSes, all CCs, exceptions to last resort, rumor says
2045+
Solaris SPARC, call/ret instructions pop and push function pointers
2046+
to an array of function pointers, far far away from the C stack as
2047+
a security measure so on SPARC this would be the contents of a random
2048+
C auto var in the caller.
2049+
2050+
IA64, with hardware assistence by the IA64, supposedly appropriate
2051+
portions of the C stack are automatically shifted into kernel space on
2052+
each function call so no callee can read or write any C auto var in its
2053+
caller. Only exception is "other_func(&some_var_this_func);" The shift
2054+
factor now excludes some_var_this_func. So the line below would SEGV.
2055+
2056+
If any bug reports come in from these old CPUs, implement the correct
2057+
platform specific way to get debugging info, or uncomment the fallback */
2058+
void * ip = *(((void **)&file_metadata)-1);
2059+
/* fallback
2060+
# if PTRSIZE == 4
2061+
void * ip = (void *)0x12345678;
2062+
# else
2063+
void * ip = (void *)0x123456789ABCDEF0;
2064+
# endif
2065+
*/
2066+
#endif
2067+
char buf [sizeof("panic: C breakpoint hit file \"%.*s\", function \"%.*s\" line %.*s CPU IP 0x%p\n")
2068+
+ (U8_MAX*3) + (PTRSIZE*2) + 1];
2069+
int out_len;
2070+
U32 f_len;
2071+
const char * file_metadata_end;
2072+
const char * p;
2073+
char * pbuf;
2074+
char * pbuf2;
2075+
U8 l;
2076+
2077+
const char * fnc_st;
2078+
const char * fnc_end;
2079+
U8 fnc_len;
2080+
2081+
const char * fn_st;
2082+
const char * fn_end;
2083+
U8 fn_len;
2084+
2085+
const char * ln_st;
2086+
const char * ln_end;
2087+
U8 ln_len;
2088+
2089+
PERL_ARGS_ASSERT_C_BP;
2090+
2091+
2092+
f_len = (U32)strlen(file_metadata);
2093+
file_metadata_end = file_metadata + f_len;
2094+
p = file_metadata;
2095+
2096+
fnc_st = p;
2097+
fnc_end = memchr(fnc_st, '*', fnc_st-file_metadata_end);
2098+
if(!fnc_end) {
2099+
fnc_st = "unknown";
2100+
fnc_end = fnc_st + STRLENs("unknown");
2101+
p = file_metadata_end;
2102+
}
2103+
else {
2104+
p = fnc_end + 1;
2105+
}
2106+
fnc_len = (U8)(fnc_end - fnc_st);
2107+
2108+
fn_st = p;
2109+
fn_end = memchr(fn_st, '*', file_metadata_end - fn_st);
2110+
if(!fn_end) {
2111+
fn_st = "unknown";
2112+
fn_end = fn_st + STRLENs("unknown");
2113+
p = file_metadata_end;
2114+
}
2115+
else {
2116+
p = fn_end + 1;
2117+
}
2118+
fn_len = (U8)(fn_end-fn_st);
2119+
2120+
ln_st = p;
2121+
ln_end = file_metadata_end;
2122+
ln_len = (U8)(ln_end - p);
2123+
if(!ln_len) {
2124+
ln_st = "unknown";
2125+
ln_len = STRLENs("unknown");
2126+
}
2127+
out_len = my_snprintf((char*)buf, sizeof(buf)-2,
2128+
"panic: C breakpoint hit file \"%.*s\", "
2129+
"function \"%.*s\" line %.*s CPU IP 0x%p",
2130+
(Size_t)fn_len, fn_st, (Size_t)fnc_len, fnc_st,
2131+
(Size_t)ln_len, ln_st, ip);
2132+
buf[out_len] = '\0'; /* MSVCRT bug don't ask, paranoia */
2133+
2134+
STMT_START {
2135+
dTHX; /* stderr+stdout, force user to see it */
2136+
Perl_warn(aTHX_ "%s", (char *)buf); /* no "\n" for max diag info */
2137+
PerlIO_flush(PerlIO_stderr());
2138+
PerlIO * out = PerlIO_stdout();
2139+
buf[out_len] = '\n'; /* force shell/terminal to print it, paranoia */
2140+
out_len++;
2141+
buf[out_len] = '\0';
2142+
PerlIO_write(out, (char *)buf, out_len);
2143+
PerlIO_flush(out); /* force shell/terminal to print it */
2144+
} STMT_END;
2145+
return;
2146+
}
2147+
20062148
/*
20072149
=for apidoc warn_sv
20082150

util.h

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,100 @@ typedef struct {
165165

166166
#endif /* USE_C_BACKTRACE */
167167

168+
/*
169+
170+
=for apidoc_section $debugging
171+
=for apidoc Amn;||C_BP
172+
173+
Prints file, C function name, and line, to I<STDOUT> and I<STDERR>.
174+
Then triggers a breakpoint in the OS specific C debugger. If the OS specific
175+
C debugger is not running, not configured correctly, not installed, or Perl
176+
is running on an unattended machine, C<C_BP> is immediatly fatal to the
177+
Perl process. "immediatly fatal" is similar to a SEGV happening.
178+
If you use a C debugger, and I<C_BP> executes, using your C debugger, you can
179+
resume execution of the Perl process, with no side effects, as if nothing
180+
happened.
181+
182+
C breakpoints implementations are very OS specific, but on most OSes, this
183+
is I<raise(SIGTRAP)> or CPU I<interrupt 3>.
184+
185+
C_BP macro should never appear in public Stable/Gold releases of Perl core
186+
or any CPAN module. Using C_BP even in a alpha release, is questionable.
187+
Smokers/CI greatly dislike SEGVs and SEGV-like abnormal process terminations
188+
which sometimes require human intervention to unfreeze the console or
189+
unattended CI tool of that unattended system. C_BP is intended for personal
190+
hacking and development, or 1 off patches sent by a lead dev to a user
191+
for troubleshooting or bug fixing some very specific problem.
192+
193+
C<C_BP> has no arguments, no return value. Use it as I<C_BP;>.
194+
195+
=cut
196+
197+
*/
198+
199+
/* __builtin_debugtrap() and __builtin_trap() for GCC and Clang have
200+
bike shedding drama, supposedly GCC and gdb made an executive decision
201+
that a software (compiled C) triggered C breakpoint, is a NO_RETURN
202+
optimized, unrecoverable hard error, and they will never impliment
203+
a software triggered breakpoint that can resume execution.
204+
AFAIK internally GCC impliments __builtin_debugtrap() and __builtin_trap()
205+
as an illegal CPU opcode, followed by NO_RETURN optimization.
206+
Supposedly GDB itself, when you set a BP in GDB, GDB will scribble
207+
"illegal opcode" in the process memory space and save whatever prior CPU
208+
op was there before. Then once the OS kernel delivers SIGILL, gdb looks at
209+
its table of breakpoints, scribbles the old good opcode over "illegal op"
210+
and resumes execution or pauses and shows you the frozen process.
211+
212+
gdb will not and has no way, to repair a foreign "illegal opcode" "problem"
213+
that came with the binary from the disk copy of the binary.
214+
The foreign "illegal opcode" was inserted at compile time by GCC.
215+
216+
Therefore, __builtin_debugtrap() and __builtin_trap() are not being used here
217+
since they don't allow resuming execution after the breakpoint/pause
218+
in the C debugger.
219+
220+
x86/x64 interrupt 3 allows resuming execution, since while GCC/gdb disagree
221+
with the decisions of Linux Kernel devs, they are reluctantly forced to
222+
to allow resuming execution for ABI/API compatiblity with the Linux Kernel.
223+
224+
For ARM, the assembly opcode for a software breakpoint seems to change with
225+
each new iPhone release. If you have a working perl on ARM development
226+
enviroment, you are welcome to add ARM specific code with correct #ifdefs.
227+
228+
If anyone wants to test __builtin_debugtrap() and __builtin_trap(), and
229+
see if execution can be resumed in a C debugger on
230+
Clang/Apple/Android/latest GCC/forks of GCC, your welcome to set #ifdefs
231+
and use __builtin_debugtrap()/__builtin_trap().
232+
*/
233+
234+
#ifdef _MSC_VER
235+
# define C_BP (void)(IsDebuggerPresent() ? __debugbreak() \
236+
: (Perl_c_bp(FUNCTION__ "*"__FILE__ "*" STRINGIFY(__LINE__)) \
237+
,__debugbreak()))
238+
#elif defined(__has_builtin) && __has_builtin(__debugbreak)
239+
# ifdef WIN32
240+
# define C_BP (void)(IsDebuggerPresent() ? __debugbreak() \
241+
: (Perl_c_bp(FUNCTION__ "*"__FILE__ "*" STRINGIFY(__LINE__)) \
242+
,__debugbreak()))
243+
# else
244+
# define C_BP (void)(Perl_c_bp(FUNCTION__ "*"__FILE__ "*" STRINGIFY(__LINE__)) \
245+
,__debugbreak())
246+
# endif
247+
#elif defined(__i386__) || defined(__x86_64__)
248+
# define C_BP (void)(Perl_c_bp(FUNCTION__ "*"__FILE__ "*" STRINGIFY(__LINE__)) \
249+
,__debugbreak_int3())
250+
251+
PERL_STATIC_INLINE void __debugbreak_int3(void) {
252+
__asm__ __volatile__("int {$}3":);
253+
}
254+
255+
#else
256+
/* last resort, has to do something useful on all platforms, and SIGTRAP
257+
in a post mortem log file is very distinct from SIGSEGV */
258+
# define C_BP (void)(Perl_c_bp(FUNCTION__ "*"__FILE__ "*" STRINGIFY(__LINE__)) \
259+
,raise(SIGTRAP))
260+
#endif
261+
168262
/* Use a packed 32 bit constant "key" to start the handshake. The key defines
169263
ABI compatibility, and how to process the vararg list.
170264

0 commit comments

Comments
 (0)