Skip to content

Commit 3890bda

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 3890bda

File tree

5 files changed

+301
-0
lines changed

5 files changed

+301
-0
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)

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.

util.c

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2003,6 +2003,206 @@ 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+
#ifdef WIN32
2035+
void * ip = _ReturnAddress();
2036+
#elif defined(__has_builtin) && __has_builtin(__builtin_return_address)
2037+
void * ip = __builtin_return_address(0);
2038+
#else
2039+
# if PTRSIZE == 4
2040+
void * ip = (void *)0x12345678;
2041+
# else
2042+
void * ip = (void *)0x123456789ABCDEF0;
2043+
# endif
2044+
#endif
2045+
//char buf [sizeof("panic: C breakpoint hit file \"%s\", function \"%s\" line %s CPU IP 0x%p\n")
2046+
// + (U8_MAX*3) + (PTRSIZE*2) + 1];
2047+
char buf [sizeof("panic: C breakpoint hit file \"%.*s\", function \"%.*s\" line %.*s CPU IP 0x%p\n")
2048+
+ (U8_MAX*3) + (PTRSIZE*2) + 1];
2049+
int out_len;
2050+
U32 f_len;
2051+
const char * file_metadata_end;
2052+
const char * p;
2053+
char * pbuf;
2054+
char * pbuf2;
2055+
U8 l;
2056+
2057+
const char * fnc_st;
2058+
const char * fnc_end;
2059+
U8 fnc_len;
2060+
2061+
const char * fn_st;
2062+
const char * fn_end;
2063+
U8 fn_len;
2064+
2065+
const char * ln_st;
2066+
const char * ln_end;
2067+
U8 ln_len;
2068+
2069+
PERL_ARGS_ASSERT_C_BP;
2070+
2071+
2072+
f_len = (U32)strlen(file_metadata);
2073+
file_metadata_end = file_metadata + f_len;
2074+
p = file_metadata;
2075+
2076+
fnc_st = p;
2077+
fnc_end = memchr(fnc_st, '*', fnc_st-file_metadata_end);
2078+
if(!fnc_end) {
2079+
fnc_st = "unknown";
2080+
fnc_end = fnc_st + STRLENs("unknown");
2081+
p = file_metadata_end;
2082+
}
2083+
else {
2084+
p = fnc_end + 1;
2085+
}
2086+
fnc_len = (U8)(fnc_end - fnc_st);
2087+
2088+
fn_st = p;
2089+
fn_end = memchr(fn_st, '*', file_metadata_end - fn_st);
2090+
if(!fn_end) {
2091+
fn_st = "unknown";
2092+
fn_end = fn_st + STRLENs("unknown");
2093+
p = file_metadata_end;
2094+
}
2095+
else {
2096+
p = fn_end + 1;
2097+
}
2098+
fn_len = (U8)(fn_end-fn_st);
2099+
2100+
ln_st = p;
2101+
ln_end = file_metadata_end;
2102+
ln_len = (U8)(ln_end - p);
2103+
if(!ln_len) {
2104+
ln_st = "unknown";
2105+
ln_len = STRLENs("unknown");
2106+
}
2107+
out_len = my_snprintf((char*)buf, sizeof(buf)-2,
2108+
"panic: C breakpoint hit file \"%.*s\", function \"%.*s\" line %.*s CPU IP 0x%p",
2109+
(Size_t)fn_len, fn_st, (Size_t)fnc_len, fnc_st, (Size_t)ln_len, ln_st, ip);
2110+
buf[out_len] = '\0';
2111+
2112+
STMT_START {
2113+
dTHX;
2114+
Perl_warn(aTHX_ "%s", (char *)buf); /* stderr+stdout, force user to see it */
2115+
PerlIO_flush(PerlIO_stderr());
2116+
PerlIO * out = PerlIO_stdout();
2117+
buf[out_len] = '\n';
2118+
out_len++;
2119+
buf[out_len] = '\0';
2120+
PerlIO_write(out, (char *)buf, out_len);
2121+
PerlIO_flush(out);
2122+
} STMT_END;
2123+
2124+
return;
2125+
f_len = (U32)strlen(file_metadata);
2126+
file_metadata_end = file_metadata + f_len;
2127+
p = file_metadata;
2128+
2129+
fnc_st = p;
2130+
fnc_end = memchr(fnc_st, '*', fnc_st-file_metadata_end);
2131+
if(!fnc_end) {
2132+
fnc_st = "unknown";
2133+
fnc_end = fnc_st + STRLENs("unknown");
2134+
p = file_metadata_end;
2135+
}
2136+
else {
2137+
fnc_end = fnc_end - 1;
2138+
p = fnc_end + 1;
2139+
}
2140+
fnc_len = (U8)(fnc_end - fnc_st);
2141+
2142+
fn_st = p;
2143+
fn_end = memchr(fn_st, '*', file_metadata_end - fn_st);
2144+
if(!fn_end) {
2145+
fn_st = "unknown";
2146+
fn_end = fn_st + STRLENs("unknown");
2147+
p = file_metadata_end;
2148+
}
2149+
else {
2150+
fn_end = fn_end - 1;
2151+
p = fn_end + 1;
2152+
}
2153+
fn_len = (U8)(fn_end-fn_st);
2154+
2155+
ln_st = p;
2156+
ln_end = file_metadata_end;
2157+
ln_len = (U8)(ln_end - p);
2158+
if(!ln_len) {
2159+
ln_st = "unknown";
2160+
ln_len = STRLENs("unknown");
2161+
}
2162+
2163+
pbuf = (char*)buf;
2164+
l = STRLENs("panic: C breakpoint hit file \"");
2165+
pbuf2 = pbuf;
2166+
pbuf += l;
2167+
Move("panic: C breakpoint hit file \"", pbuf2, l, char);
2168+
2169+
pbuf2 = pbuf;
2170+
pbuf += fn_len;
2171+
Move(fn_st, pbuf2, fn_len, char);
2172+
2173+
l = STRLENs("\", function \"");
2174+
pbuf2 = pbuf;
2175+
pbuf += l;
2176+
Move("\", function \"", pbuf2, l, char);
2177+
2178+
pbuf2 = pbuf;
2179+
pbuf += fnc_len;
2180+
Move(fnc_st, pbuf2, fnc_len, char);
2181+
2182+
l = STRLENs("\" line ");
2183+
pbuf2 = pbuf;
2184+
pbuf += l;
2185+
Move("\" line ", pbuf2, l, char);
2186+
2187+
pbuf2 = pbuf;
2188+
pbuf += ln_len;
2189+
Move(ln_st, pbuf2, ln_len, char);
2190+
2191+
pbuf += sprintf(pbuf," CPU IP 0x%p", ip);
2192+
Perl_warn_nocontext((char *)buf); /* stderr+stdout, force user to see it */
2193+
STMT_START {
2194+
dTHX;
2195+
PerlIO_flush(PerlIO_stderr());
2196+
PerlIO * out = PerlIO_stdout();
2197+
*pbuf = '\n';
2198+
pbuf++;
2199+
*pbuf = '\0';
2200+
pbuf++;
2201+
PerlIO_write(out, (char *)buf, pbuf-((char *)buf)-1);
2202+
PerlIO_flush(out);
2203+
} STMT_END;
2204+
}
2205+
20062206
/*
20072207
=for apidoc warn_sv
20082208

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)