1. BREAKing into Euphoria
- Posted by Andy Drummond <andy at ?estrel?ele.com> Sep 11, 2007
- 2173 views
I have a situation where sometimes a program goes off into the wild blue yonder and I have no idea why. Is there any way that a keypress like BREAK or SysReq could be picked up by the interpreter and drop you into the debug window? I agree that you'd need to enable this so that nice distributed code couldn't get hacked, but it seems as if it should be a pretty trivial addition to the Euphoria interpreter itself. Has anyone done this, or is there any reason why it is not really practical? Andy
2. Re: BREAKing into Euphoria
- Posted by Salix <salix at fr?email.hu> Sep 11, 2007
- 2141 views
Andy Drummond wrote: > Has anyone done this, or is there any reason why it is > not really practical? Usullay my scripts run faster than I can press Break. Adding a trace(1) line to my code always worked fine. If I wasn't sure where to put it I simply added a trace_if_timeout(60) routine to the most suspicious loops. Regards, Salix
3. Re: BREAKing into Euphoria
- Posted by Andy Drummond <andy at kestreltele.co?> Sep 11, 2007
- 2114 views
Salix wrote: > > Andy Drummond wrote: > > Has anyone done this, or is there any reason why it is > > not really practical? > > Usullay my scripts run faster than I can press Break. > > Adding a trace(1) line to my code always worked fine. > If I wasn't sure where to put it I simply added a > trace_if_timeout(60) routine to the most suspicious > loops. > > Regards, > > Salix Ah, but I have this enormous program and somewhere, somehow, it is locking up in a loop. If I went around putting in timeouts & traces I'd take longer than modifying the source for the interpreter and recompiling it and using a ctl-C or whatever. No, what I *need* is a way of getting the interpreter to enter its trace routine from keyboard use and not from trace(). Even then it is a problem because it is likely the program is not looking at the keyboard, so the interpreter would have to go check the keyboard itself for a break character. Apart from all that, it is likely that the loop is not supposed to be a loop anyway, so I wouldn't know to trace it. This is the ultimate cock-up cracker. I could use the profiler if I could get the program to stop cleanly rather than saying "This program is not responding - terminate anyway (Y/N)" which dumps anything it had available into a black hole. So thanks anyway, but - I need to ask the question again. Andy
4. Re: BREAKing into Euphoria
- Posted by Robert Craig <rds at RapidEup?oria.?om> Sep 11, 2007
- 2144 views
Andy Drummond wrote: > Ah, but I have this enormous program and somewhere, somehow, > it is locking up in a loop. If I went around putting in > timeouts & traces I'd take longer than modifying the source > for the interpreter and recompiling it and using a ctl-C or > whatever. No, what I *need* is a way of getting the > interpreter to enter its trace routine from keyboard use > and not from trace(). Even then it is a problem because it > is likely the program is not looking at the keyboard, so > the interpreter would have to go check the keyboard itself > for a break character. > Apart from all that, it is likely that the loop is not supposed > to be a loop anyway, so I wouldn't know to trace it. > This is the ultimate cock-up cracker. I could use the profiler > if I could get the program to stop cleanly rather than saying > "This program is not responding - terminate anyway (Y/N)" > which dumps anything it had available into a black hole. > So thanks anyway, but - I need to ask the question again. Have you tried: with trace trace(3) It will slow your program down, but you'll capture the last 500 statements in a file, ctrace.out After forcibly killing it, you might be able to see in ctrace.out what it was doing when it became non-responsive. Regards, Rob Craig Rapid Deployment Software http://www.RapidEuphoria.com
5. Re: BREAKing into Euphoria
- Posted by Andy Drummond <andy at kestrelt?l?.com> Sep 11, 2007
- 2152 views
Robert Craig wrote: > > > Have you tried: > with trace > trace(3) > > It will slow your program down, > but you'll capture the last 500 statements > in a file, ctrace.out > > After forcibly killing it, you might be able to > see in ctrace.out what it was doing when it > became non-responsive. > > Regards, > Rob Craig > Rapid Deployment Software > <a href="http://www.RapidEuphoria.com">http://www.RapidEuphoria.com</a> Ah, so trace(3)closes the ctrace.out file after each write, does it? That will do as well, I think, Rob, so thanks very much for the post. I will give it a try. A well known law says that I won't get the code to lock up with trace(3) running, of course! I shall have to add "with trace" to every include file, I guess..... Thanks, Andy
6. Re: BREAKing into Euphoria
- Posted by Pete Lomax <petelomax at blueyonder.co.?k> Sep 11, 2007
- 2126 views
- Last edited Sep 12, 2007
Andy Drummond wrote: > > Ah, but I have this enormous program and somewhere, somehow, > it is locking up in a loop. If I went around putting in > timeouts & traces I'd take longer than modifying the source > for the interpreter and recompiling it and using a ctl-C or > whatever. No, what I *need* is a way of getting the > interpreter to enter its trace routine from keyboard use > and not from trace(). I got some "interesting" results (random crashes mainly) from this, however enough of it works to show that something is possible (comments, Rob?): Maybe there is a generic problem with trace in call_back routines??
-- NOTE: this is probably incompatible with allow_break/check_break(). with trace include misc.e constant CTRL_C_EVENT = 0, CTRL_BREAK_EVENT = 1 function HandlerRoutine(integer event) if event = CTRL_C_EVENT or event = CTRL_BREAK_EVENT then trace(1) sleep(2) return 1 -- handled end if return 0 -- not handled end function atom kernel32, xSetConsoleCtrlHandler, cbHR include dll.e kernel32 = open_dll("kernel32.dll") xSetConsoleCtrlHandler = define_c_func(kernel32,"SetConsoleCtrlHandler", {C_POINTER, -- PHANDLER_ROUTINE pHandlerRoutine, // address of handler function C_INT}, -- BOOL fAdd // handler to add or remove C_INT) -- BOOL cbHR = call_back(routine_id("HandlerRoutine")) if not c_func(xSetConsoleCtrlHandler,{cbHR,1}) then ?9/0 end if with trace while 1 do puts(1,"sleeping\n") sleep(2) puts(1,"awake\n") end while
I suspect that if you could somehow call the trace(1) in HandlerRoutine but not then trace any lines in that code, it might actually work. When not interpreting source, or under an allow_break(False), then Ctrl Break should not attempt to fire up the trace window, otherwise it should. I don't think changing Ctrl C is a good idea, too easy to accidentally hit, plus eg Edita happily uses it for Copy w/out problems as is. Regards, Pete
7. Re: BREAKing into Euphoria
- Posted by Robert Craig <rds at RapidEu?horia.co?> Sep 11, 2007
- 2119 views
- Last edited Sep 12, 2007
Andy Drummond wrote: > Robert Craig wrote: > > Have you tried: > > with trace > > trace(3) > > > > It will slow your program down, > > but you'll capture the last 500 statements > > in a file, ctrace.out > > > > After forcibly killing it, you might be able to > > see in ctrace.out what it was doing when it > > became non-responsive. > > Ah, so trace(3)closes the ctrace.out file after each write, does it? It flushes the file. Same effect as closing it. > That will do as well, I think, Rob, so thanks very much for the post. > I will give it a try. A well known law says that I won't get the code > to lock up with trace(3) running, of course! I shall have to add > "with trace" to every include file, I guess..... Not really, just the top of your main program file, unless you want to speed things up a bit by not tracing certain frequently-executed code. Regards, Rob Craig Rapid Deployment Software http://www.RapidEuphoria.com
8. Re: BREAKing into Euphoria
- Posted by Robert Craig <rds at R?pidEu?horia.com> Sep 11, 2007
- 2116 views
- Last edited Sep 12, 2007
Pete Lomax wrote: > > Andy Drummond wrote: > > > > Ah, but I have this enormous program and somewhere, somehow, > > it is locking up in a loop. If I went around putting in > > timeouts & traces I'd take longer than modifying the source > > for the interpreter and recompiling it and using a ctl-C or > > whatever. No, what I *need* is a way of getting the > > interpreter to enter its trace routine from keyboard use > > and not from trace(). > > I got some "interesting" results (random crashes mainly) from this, however > enough of it works to show that something is possible (comments, Rob?): > Maybe there is a generic problem with trace in call_back routines?? Possibly. I'm not sure. Regards, Rob Craig Rapid Deployment Software http://www.RapidEuphoria.com
9. Re: BREAKing into Euphoria
- Posted by Pete Lomax <petelomax at blueyond?r.co?uk> Sep 12, 2007
- 2125 views
Robert Craig wrote: > > Pete Lomax wrote: > > > > I got some "interesting" results (random crashes mainly) from this, however > > enough of it works to show that something is possible (comments, Rob?): > > Maybe there is a generic problem with trace in call_back routines?? > > Possibly. I'm not sure. I guess SetConsoleCtrlHandler is invoking the Eu code I posted much like OS threads, so if it is not thread-safe then random crashes will occur. Hence any such code would have to be in the back end rather than Eu code, unless something can be done along the lines of task_yield(). Regards, Pete
10. Re: BREAKing into Euphoria
- Posted by Robert Craig <rds at Ra?idEu?horia.com> Sep 12, 2007
- 2120 views
Pete Lomax wrote: > Robert Craig wrote: > > Pete Lomax wrote: > > > > > > I got some "interesting" results (random crashes mainly) from this, > > > however > > > enough of it works to show that something is possible (comments, Rob?): > > > Maybe there is a generic problem with trace in call_back routines?? > > > > Possibly. I'm not sure. > > I guess SetConsoleCtrlHandler is invoking the Eu code I posted much like OS > threads, so if it is not thread-safe then random crashes will occur. Hence any > such code would have to be in the back end rather than Eu code, unless > something > can be done along the lines of task_yield(). If you break out of the interpreter at a random point, and then re-enter the interpreter, there could be problems. It might have been in the middle of some sensitive operation that was not completed properly. Multitasking in Euphoria only uses one O/S thread, and a task switch only happens at a task_yield() so everything is safe. Regards, Rob Craig Rapid Deployment Software http://www.RapidEuphoria.com
11. Re: BREAKing into Euphoria
- Posted by Al Getz <Xaxo at ?o?.com> Sep 12, 2007
- 2130 views
Andy Drummond wrote: > > I have a situation where sometimes a program goes off > into the wild blue yonder and I have no idea why. > Is there any way that a keypress like BREAK or SysReq > could be picked up by the interpreter and drop you into > the debug window? I agree that you'd need to enable this > so that nice distributed code couldn't get hacked, but > it seems as if it should be a pretty trivial addition to > the Euphoria interpreter itself. > Has anyone done this, or is there any reason why it is > not really practical? > Andy Hi Andy, If nothing else works, add a few enumerated printf statements that print to a debug file. Each printf should print at least a number like: printf(fn,"%d:",4) This way once you close the program you can go to the debug file and see where your program last encountered a printf statement like this. Of course you need to append the file rather than keep it open or some of your printf's wont printf. You could also try my EuDebug program that runs Eu programs in debug mode. It's a little dated already however so if you are using some more recently added Eu functions it may not work. Take care, Al E boa sorte com sua programacao Euphoria! My bumper sticker: "I brake for LED's" From "Black Knight": "I can live with losing the good fight, but i can not live without fighting it". "Well on second thought, maybe not."
12. Re: BREAKing into Euphoria
- Posted by Andy Drummond <andy at k?strel?ele.com> Sep 12, 2007
- 2120 views
Pete Lomax wrote: > > I got some "interesting" results (random crashes mainly) from this, however > enough of it works to show that something is possible (comments, Rob?): > Maybe there is a generic problem with trace in call_back routines?? > > }}} <eucode> > -- NOTE: this is probably incompatible with allow_break/check_break(). > with trace > > include misc.e > > constant CTRL_C_EVENT = 0, > CTRL_BREAK_EVENT = 1 > > function HandlerRoutine(integer event) > if event = CTRL_C_EVENT > or event = CTRL_BREAK_EVENT then > trace(1) > sleep(2) > return 1 -- handled > end if > return 0 -- not handled > end function > > atom kernel32, xSetConsoleCtrlHandler, cbHR > include dll.e > > kernel32 = open_dll("kernel32.dll") > > xSetConsoleCtrlHandler = define_c_func(kernel32,"SetConsoleCtrlHandler", > {C_POINTER, -- PHANDLER_ROUTINE pHandlerRoutine, // address of handler > function > C_INT}, -- BOOL fAdd // handler to add or remove > C_INT) -- BOOL > > cbHR = call_back(routine_id("HandlerRoutine")) > if not c_func(xSetConsoleCtrlHandler,{cbHR,1}) then ?9/0 end if > > with trace > > while 1 do > puts(1,"sleeping\n") > sleep(2) > puts(1,"awake\n") > end while > </eucode> {{{ > > I suspect that if you could somehow call the trace(1) in HandlerRoutine but > not then trace any lines in that code, it might actually work. > > When not interpreting source, or under an allow_break(False), then Ctrl Break > should not attempt to fire up the trace window, otherwise it should. > I don't think changing Ctrl C is a good idea, too easy to accidentally hit, > plus eg Edita happily uses it for Copy w/out problems as is. > > Regards, > Pete Pete, That look pretty much what I was after. I understand that it might well not be possible to trace once the break has been executed, but if I can tell where I am in the program then I can start the safer but more laborious technique of putting in trace(1) - or even trace(3) - calls. At present I have no idea where I am executing when everything dies on me. I just need to go Click! and find out which line of the program was executing at the time. We will see... Andy
13. Re: BREAKing into Euphoria
- Posted by Andy Drummond <andy at kestreltel?.co?> Sep 12, 2007
- 2143 views
Pete Lomax wrote: > > > I got some "interesting" results (random crashes mainly) from this, however > enough of it works to show that something is possible (comments, Rob?): > Maybe there is a generic problem with trace in call_back routines?? > ........ > I suspect that if you could somehow call the trace(1) in HandlerRoutine but > not then trace any lines in that code, it might actually work. > > When not interpreting source, or under an allow_break(False), then Ctrl Break > should not attempt to fire up the trace window, otherwise it should. > I don't think changing Ctrl C is a good idea, too easy to accidentally hit, > plus eg Edita happily uses it for Copy w/out problems as is. > > Regards, > Pete Yes, well, I see what you mean now. The control-C or control-Break keys do indeed break the program but there is no evidence that it ever reaches the code you sent. So ... not much further forward. Andy
14. Re: BREAKing into Euphoria
- Posted by Pete Lomax <petelomax at bluey?nd?r.co.uk> Sep 12, 2007
- 2125 views
Andy Drummond wrote: > Yes, well, I see what you mean now. The control-C or control-Break keys do > indeed break the program but there is no evidence that it ever reaches the > code you sent. So ... not much further forward. What I get here, not every time though, is a partial/garbled trace window and fatal exception, eg http://palacebuilders.pwp.blueyonder.co.uk/break.gif Nevermind, I'll email you privately if I get any further with this. Regards, Pete
15. Re: BREAKing into Euphoria
- Posted by CChris <christian.cuvier at a?ri?ulture.gouv.fr> Sep 12, 2007
- 2109 views
Andy Drummond wrote: > > Pete Lomax wrote: > > > > > > I got some "interesting" results (random crashes mainly) from this, however > > enough of it works to show that something is possible (comments, Rob?): > > Maybe there is a generic problem with trace in call_back routines?? > > ........ > > I suspect that if you could somehow call the trace(1) in HandlerRoutine but > > not then trace any lines in that code, it might actually work. > > > > When not interpreting source, or under an allow_break(False), then Ctrl > > Break > > should not attempt to fire up the trace window, otherwise it should. > > I don't think changing Ctrl C is a good idea, too easy to accidentally hit, > > plus eg Edita happily uses it for Copy w/out problems as is. > > > > Regards, > > Pete > > Yes, well, I see what you mean now. The control-C or control-Break keys do > indeed break the program but there is no evidence that it ever reaches the > code you sent. So ... not much further forward. > Andy You can use the Eu-in-Eu eu.ex program - perhaps renamed to .exw - to run your code, after modifying it so as it tells you (probably by writing to a debug file) whenever it enters or exit a routine, in the opPROC() procedure from execute.e. If your code is quite linear in shape, you can , at the same time, do some printf() to the same debug file at critical points. CChris
16. Re: BREAKing into Euphoria
- Posted by Matt Lewis <matthewwalkerlewis at ??ail.com> Sep 12, 2007
- 2134 views
Pete Lomax wrote: > > Andy Drummond wrote: > > Yes, well, I see what you mean now. The control-C or control-Break keys do > > indeed break the program but there is no evidence that it ever reaches the > > code you sent. So ... not much further forward. > > What I get here, not every time though, is a partial/garbled trace window and > fatal exception, eg <a > href="http://palacebuilders.pwp.blueyonder.co.uk/break.gif">http://palacebuilders.pwp.blueyonder.co.uk/break.gif</a> > Nevermind, I'll email you privately if I get any further with this. Yes, the problem, as you previously sorta mentioned, is that windows creates a new thread and uses that thread to call the handler. It's probably fairly straightforward to manage this in a thread-safe way within the C-backend, but I don't see a nice way to do this in a purely Eu-based fashion. I think the way to go is to use trace(3) (was about to post it until I noticed that Rob beat me to it). As for having to put "with trace" everywhere: You'd have to do this regardless (the top of your main would work, unless you have any "without trace" statements elsewhere in your code. Otherwise, Euphoria doesn't generate the IL that drives the trace screen. trace(3) is clearly the way to go. Let it run for a while, and check out the file. It should be numbingly repetitive if you're really in an infinite loop. You may want/need to "without trace" some library routines to cut down on the noise. Even better, only enable tracing in routines that use while or for loops. You could probably make an automated tool to insert/remove the with/without statements, assuming you've got a lot of code/files to deal with. Matt
17. Re: BREAKing into Euphoria
- Posted by Andy Drummond <andy at ?estrelt?le.com> Sep 12, 2007
- 2130 views
Matt Lewis wrote: > > > Yes, the problem, as you previously sorta mentioned, is that windows creates > a new thread and uses that thread to call the handler. It's probably > fairly straightforward to manage this in a thread-safe way within the > C-backend, but I don't see a nice way to do this in a purely Eu-based > fashion. > > I think the way to go is to use trace(3) (was about to post it until I > noticed that Rob beat me to it). > > As for having to put "with trace" everywhere: You'd have to do this > regardless (the top of your main would work, unless you have any > "without trace" statements elsewhere in your code. Otherwise, Euphoria > doesn't generate the IL that drives the trace screen. > > trace(3) is clearly the way to go. Let it run for a while, and check out > the file. It should be numbingly repetitive if you're really in an > infinite loop. You may want/need to "without trace" some library routines > to cut down on the noise. Even better, only enable tracing in routines > that use while or for loops. You could probably make an automated tool > to insert/remove the with/without statements, assuming you've got a lot > of code/files to deal with. > > Matt Yes, I am pretty sure I'm going to have to do just that. I suppose with a fairly quick PC even trace(3) shouldn't make it unusable. I may create a mind-blowingly enormous file, though, unless Rob has been especially clever. I will let you know how that pans out... Andy
18. Re: BREAKing into Euphoria
- Posted by Matt Lewis <matthewwalkerlewis at gma?l.co?> Sep 12, 2007
- 2182 views
Andy Drummond wrote: > > Yes, I am pretty sure I'm going to have to do just that. I suppose with > a fairly quick PC even trace(3) shouldn't make it unusable. I may create > a mind-blowingly enormous file, though, unless Rob has been especially > clever. It will probably seem really slow. But you should only need to do that once, and then you can zero in on the problem. The file never grows beyond a certain size (~40K) since it only saves a total of 500 lines. Check out the docs for more details, but it should be pretty obvious once you start using it. Just search the file for "THE END" to see where it stopped logging. Matt
19. Re: BREAKing into Euphoria
- Posted by abuaf Jan 24, 2022
- 1878 views
Quoted code was ok, but you can't invoke trace in a callback. You can queue a trace by patching TraceOn=1 in eu client app (i got the virtual address from gnu disasm - sorry just cut and paste the asm into euphoria - anyone rewrite this in euphoria?); so this works for official eu 4.1.0 download (https://github.com/EuphoriaLanguages/Friendly_Flexible_Fast/raw/OpenEuphoria/euphoria-4.1.0-x86.exe). Update allow_break() machine call should be enhanced to take &2= set TraceOn, &4= color_trace=1, but i'm too lazy to recompile the sources. Anyway my code below correctly pops up the debugger at the next trace'able code whenever the user presses the break keys. It also demonstrates iup timeslicing some heavy foreground processing while updating the iup (windows) gui message pipe to minimize iup callback 'involvement'. If you press 'q' when entering text into the control, you'll break out to the debugger which allows you focused debugging of gui elements; see the 'tr()' conditional trace func. If press break in the console, you'll popup the debugger at the next iup client callback event handler. Now only if the debugger had pageup/down???
-- test.ex IupText Example in C --Creates a IupText that shows asterisks instead of characters (password-like)./ include euphoria/debug/debug.e include std/os.e --include dll.e --include machine.e include gui/iup/iup.e include gui/iup/iup_config.e include std/console.e sequence add_code = { --do trace(1) in "without trace" code, so the debugger pops up in the next "with trace" code. -- Eu trace(1) in "without trace" code is optimized away, and debugger never pops up. #c7, #05, #20, #1a, #5b, #00, -- movl $0x1,0x5b1a20 ie TraceOn=1 #01, #00, #00, #00, #c7, #05, #40, #14, #5b, #00, -- movl $0x1,0x5b1440 ie color_trace = TRUE; --ie ensures the debugger pops up in color (not static uninit value of 0 ie B/W) #01, #00, #00, #00, --#C2, #00, #08 * (platform() = WINDOWS) -- ret 8 #c3 -- ret } atom code_space = allocate_code(add_code) integer code_space_rid = define_c_proc("", code_space, {}) global procedure tr(object condition={}, sequence errorMessage={}) --atom condition <> 0, then print errorMessage, else ignore. Eg tr(1>0, {"%s %d", "five=", 5}) --string 'condition' for simple error message if atom(condition) and condition = 0 then return end if --failed condition if its numeric if length(errorMessage) >= 2 and sequence(errorMessage[1]) then --formatting string, args ... errorMessage=sprintf(errorMessage[1], errorMessage[2..$]) end if if atom(condition) then condition=sprintf("%d", condition) elsif length(condition) then errorMessage=sprintf("%s %s", {condition, errorMessage}) condition="" end if if length(errorMessage) or length(condition) then --non-empty string, so elaborate error printf(1, "\ntr(%s): %s\n", {condition, errorMessage}) end if c_func(xSetForegroundWindow, {hCon} ) --switch to (debugger) console window c_proc(code_space_rid, {}) --trace out to the debugger at next "with trace" code. end procedure function breakProc(integer event) c_proc(code_space_rid, {}) --trace out to the debugger at next "with trace" code. return 1 --handled the control signal so return TRUE for no further action. end function type Ihandle( object x ) return 1 end type with trace Ihandle text, dlg, pwd sequence password function k_any( Ihandle self, atom c ) tr(c='q', {"Debugging IUP client callback, c=%d", c}) switch (c) do case K_BS then integer size = length(password) if equal(size , 0) then return IUP_IGNORE end if password[size-1] = 0 IupSetAttribute(pwd, "VALUE", password) return IUP_DEFAULT case K_CR then case K_SP then case K_ESC then case K_INS then case K_DEL then case K_TAB then case K_HOME then case K_UP then case K_PGUP then case K_LEFT then case K_MIDDLE then case K_RIGHT then case K_END then case K_DOWN then case K_PGDN then return IUP_IGNORE case else return IUP_DEFAULT end switch return 1 end function function action( Ihandle self, integer c, atom after ) if (c) then integer size = length(password) password[size-1] = c password[size] = 0 IupSetAttribute(pwd, "VALUE", password) end if return K_asterisk end function --atom kernel32, xSetConsoleCtrlHandler, cbHR atom xSetConsoleCtrlHandler, cbHR procedure noIup() while 1 do puts(1,"sleeping press q to quit, break to debug\n") sleep(2) puts(1,"awake\n") if get_key() = 'q' then exit end if end while end procedure without trace function main( object argc, object argv ) IupOpen( argc, argv ) password = repeat(0,100) xSetConsoleCtrlHandler = define_c_func(kernel32,"SetConsoleCtrlHandler", {C_POINTER, -- PHANDLER_ROUTINE pHandlerRoutine, // address of handler function C_INT}, -- BOOL fAdd // handler to add or remove C_INT) -- BOOL cbHR = call_back(routine_id("breakProc")) --{’+’, id} for cdecl if not c_func(xSetConsoleCtrlHandler,{cbHR,1}) then ?9/0 end if noIup() text = IupText(NULL) IupSetAttribute(text, "SIZE", "200x") IupSetCallback(text, "ACTION", Icallback("action")) IupSetCallback(text, "K_ANY", Icallback("k_any")) pwd = IupText(NULL) IupSetAttribute(pwd, "READONLY", "YES") IupSetAttribute(pwd, "SIZE", "200x") dlg = IupDialog(IupVbox({text, pwd, NULL})) IupSetAttribute(dlg, "TITLE", "IupText") IupShowXY(dlg, IUP_CENTER, IUP_CENTER) -- IupMainLoop() --blocks this foreground process until user closes GUI loop do ? hCon & date() printf(1, "Press break to debug ") sleep(.01) --do a short (10mSec) bit of forground timeslicing here until IupLoopStep() = IUP_CLOSE --docs say this should be called within a callback end loop IupClose() return EXIT_SUCCESS end function constant kernel32 = open_dll( "kernel32.dll" ) constant xGetConsoleWindow = define_c_func( kernel32, "GetConsoleWindow", {}, C_ULONG ) atom hCon = c_func( xGetConsoleWindow, {} ) constant user32 = open_dll( "user32" ) --constant xSetForegroundWindow = define_c_func( user32, "SetForegroundWindow", {C_ULONG}, C_ULONG ) constant xSetForegroundWindow = define_c_func( user32, "SetForegroundWindow", {C_ULONG}, C_UINT ) -- BOOL SetForegroundWindow(HWND) sequence cmd = command_line() sequence argv = cmd[3..$] integer argc = length( argv ) main( argc, argv )
Pete Lomax wrote: > > I got some "interesting" results (random crashes mainly) from this, however > enough of it works to show that something is possible (comments, Rob?): > Maybe there is a generic problem with trace in call_back routines?? > > }}} <eucode> > -- NOTE: this is probably incompatible with allow_break/check_break(). > with trace > > include misc.e > > constant CTRL_C_EVENT = 0, > CTRL_BREAK_EVENT = 1 > > function HandlerRoutine(integer event) > if event = CTRL_C_EVENT > or event = CTRL_BREAK_EVENT then > trace(1) > sleep(2) > return 1 -- handled > end if > return 0 -- not handled > end function > > atom kernel32, xSetConsoleCtrlHandler, cbHR > include dll.e > > kernel32 = open_dll("kernel32.dll") > > xSetConsoleCtrlHandler = define_c_func(kernel32,"SetConsoleCtrlHandler", > {C_POINTER, -- PHANDLER_ROUTINE pHandlerRoutine, // address of handler > function > C_INT}, -- BOOL fAdd // handler to add or remove > C_INT) -- BOOL > > cbHR = call_back(routine_id("HandlerRoutine")) > if not c_func(xSetConsoleCtrlHandler,{cbHR,1}) then ?9/0 end if > > with trace > > while 1 do > puts(1,"sleeping\n") > sleep(2) > puts(1,"awake\n") > end while > </eucode> {{{ > > I suspect that if you could somehow call the trace(1) in HandlerRoutine but > not then trace any lines in that code, it might actually work. > > When not interpreting source, or under an allow_break(False), then Ctrl Break > should not attempt to fire up the trace window, otherwise it should. > I don't think changing Ctrl C is a good idea, too easy to accidentally hit, > plus eg Edita happily uses it for Copy w/out problems as is. > > Regards, > Pete Pete, That look pretty much what I was after. I understand that it might well not be possible to trace once the break has been executed, but if I can tell where I am in the program then I can start the safer but more laborious technique of putting in trace(1) - or even trace(3) - calls. At present I have no idea where I am executing when everything dies on me. I just need to go Click! and find out which line of the program was executing at the time. We will see... Andy
20. Re: BREAKing into Euphoria
- Posted by abuaf Feb 08, 2022
- 1736 views
An update. Dont mind the cd lib stuff. Shows usage of iupmglplot. Also shows how to drastically reduce IUP message polling CPU by reduced polling during idleness.
with define DEBUG_TYPE --popup debugger after type check failures otherwise comment out for the normal crash include euphoria/debug/debug.e include std/math.e include std/console.e include std/os.e --include dll.e --include machine.e include gui/iup/iup.e include gui/iup/iup_config.e include gui/iup/iup_mglplot.e /* include gui/iup/cd.e include gui/iup/cdiup.e */ constant user32 = open_dll( "user32" ) constant xSetForegroundWindow = define_c_func( user32, "SetForegroundWindow", {C_ULONG}, C_UINT ) -- BOOL SetForegroundWindow(HWND) constant xGetForegroundWindow = define_c_func( user32, "GetForegroundWindow", {}, C_UINT ) -- HWND GetForegroundWindow(void) atom focusOld=0 --start with NULL (invalid) atom debugTime=0 procedure traceOn(integer val) poke4(0x5b1a20, val) --ie TraceOn=val poke4(0x5b1440, 1) --color_trace = TRUE; --ie ensures the debugger pops up in color (not static uninit value of 0 ie B/W) end procedure global procedure goDebug(integer focusDebug) --go to the debugger at the next trac'able line focusOld=c_func(xGetForegroundWindow) --save the current focus'ed win handle debugTime=time() if focusDebug then c_func(xSetForegroundWindow, {hCon} ) else -- don't want to set focus to debugger; ie waiting for some iup gui user activity to enter trace'able code, so end if --switch to (debugger) console window traceOn(1) --TraceOn=1 ie trace out to the debugger at next "with trace" code. end procedure global procedure tr(object condition={}, sequence errorMessage={}) --atom condition <> 0, then print errorMessage, else ignore. Eg tr(1>0, {"%s %d", "five=", 5}) --string 'condition' for simple error message if atom(condition) and condition = 0 then return end if --failed condition if its numeric if length(errorMessage) >= 2 and sequence(errorMessage[1]) then --formatting string, args ... errorMessage=sprintf(errorMessage[1], errorMessage[2..$]) end if if atom(condition) then condition=sprintf("%d", condition) elsif length(condition) then errorMessage=sprintf("%s %s", {condition, errorMessage}) condition="" end if if length(errorMessage) or length(condition) then --non-empty string, so elaborate error printf(1, "\ntr(%s): %s\n", {condition, errorMessage}) end if goDebug(1) end procedure atom breakTime=0 function breakFunc(integer event) puts(1, "Break pressed... ") breakTime -= time() if breakTime > -1 then traceOn(0) --cancel trace out to debugger (set first press), so terminate immediately puts(1, "quick break repeat; exiting app...\n") return 0 --Quickly repeated break, so act as if break not handled by this fn, ie pass on to next handler (eu terminates) end if breakTime = time() --update timestamp goDebug(1) return 1 --handled the control signal so return TRUE for no further action. end function without trace --we dont trace inside type checks ifdef DEBUG_TYPE then -- pinpoints the type error location with the debugger function typeEnd() goDebug(1) --popup the debugger at the next traceable line return 1 --no type abort; continue running end function elsedef function typeEnd() return 0 --abort with type check error end function end ifdef type Ihandle(atom x) if x>NULL then --ok positive if trunc(x) = x then return 1 end if --no fractional part so x ok so continue running end if printf(1, "type Ihandle: bad value %d\n", {x}) return typeEnd() end type type cdCanvas(atom x) if x>NULL then --ok positive if trunc(x) = x then return 1 end if --no fractional part so x ok so continue running end if printf(1, "type cdCanvas: bad value %d\n", {x}) return typeEnd() end type type cdContext(atom x) if x>NULL then --ok positive if trunc(x) = x then return 1 end if --no fractional part so x ok so continue running end if printf(1, "type cdContext: bad value %d\n", {x}) return typeEnd() end type with trace Ihandle plot function k_any( Ihandle self, atom c ) tr(c='q', {"Debugging IUP client callback, c=%d", c}) return IUP_DEFAULT -- return IUP_IGNORE end function function action( Ihandle self, integer c, atom after ) if (c) then end if return K_asterisk end function --atom kernel32, xSetConsoleCtrlHandler, cbHR atom xSetConsoleCtrlHandler, cbHR procedure noIup() integer key while 1 do puts(1,"sleeping press q to quit, break to debug\n") sleep(2) key=get_key() printf(1,"awake, key=%d\n", {key}) if key = 'q' then exit end if end while end procedure function iupStart( object argc, object argv ) IupOpen( argc, argv ) IupMglPlotOpen() --init IupMGLPlot library plot = IupMglPlot() --creates a empty initial data set IupMglPlotBegin(plot,2) for i=1 to 70 do IupMglPlotAdd2D(plot, i, i*i) end for {}=IupMglPlotEnd(plot) --puts the plot data into _plot. Now we can use IupSetAttribute() to modify plot data -- IupSetAttribute(text, "SIZE", "200x") -- IupSetCallback(text, "ACTION", Icallback("action")) -- IupSetCallback(text, "K_ANY", Icallback("k_any")) --IupSetAttribute(plot, "AXS_X", "NO") --ok --IupSetAttribute(plot, "LEGEND", "YES") --ok --IupSetAttribute(plot, "GRID", "XY") --ok IupSetAttribute(plot, "DS_COLOR", "255 0 255") --ok "R G B" IupSetAttribute(plot, "DS_MODE", "BAR") --ok IupSetAttribute(plot, "REDRAW", NULL) /* Ihandle iCanvas = IupCanvas() --Ihandle *cnvs -- cdcanvas = cdCreateCanvas( CD_IUP, cnvs ); --static cdCanvas *cdcanvas . H:\dl\graphics\CanvasDraw\v5.9\cd-5.9_Sources.zip;cd\include\cd.h; cdCreateCanvas(cdContext *context, void *data); cdContext ctx=CD_IUP() cdCanvas canvas = cdCreateCanvas(ctx, iCanvas ) --static cdCanvas *cdcanvas . H:\dl\graphics\CanvasDraw\v5.9\cd-5.9_Sources.zip;cd\include\cd.h; cdCreateCanvas(cdContext *context, void *data); cdCanvasForeground(canvas, CD_BLUE) -- cdCanvasLineWidth(canvas, 3); -- cdCanvasLineStyle(canvas, CD_CONTINUOUS); cdCanvasRect(canvas, 0, 200, 0, 200) -- cdKillCanvas(canvas) -- IupMglPlotNewDataSet(plot, 2) --create another data set */ Ihandle dlg = IupDialog(plot) --create a dialogue element to put the plot into IupSetAttributes(dlg, "RASTERSIZE=640x480") IupSetAttribute(dlg, "TITLE", "temperature/moisture") -- IupSetAttribute(plot, "GRID=YES, MARGINBOTTOM=80, AXS_XTICKROTATENUMBER=YES, MARGINRIGHT=80") -- IupSetAttribute(plot, "READONLY", "NO") -- IupSetAttribute(dlg, "MARGIN", "100x100") IupShowXY(dlg, IUP_CENTER, IUP_CENTER) return EXIT_SUCCESS end function without trace --we dont want to debug iupIdle or the iup event polling loop atom idleSleep=0 function iupIdle() idleSleep=.11 --iup is idle so increase the polling delay return IUP_DEFAULT end function constant kernel32 = open_dll( "kernel32.dll" ) constant xGetConsoleWindow = define_c_func( kernel32, "GetConsoleWindow", {}, C_ULONG ) atom hCon = c_func( xGetConsoleWindow, {} ), hIup=0, hCurrent=0 constant xGetNumberOfConsoleInputEvents = define_c_func( kernel32, "GetNumberOfConsoleInputEvents", {C_ULONG , C_POINTER}, C_INT) --HANDLE hConsoleInput, _Out_ LPDWORD lpNumberOfEventsRead constant dwordVar = allocate(4) ------------------ CONSOLE INPUT FNs integer xReadConsoleInput = define_c_func( kernel32, "ReadConsoleInputW", {C_ULONG , C_ULONG, C_INT, C_POINTER}, C_INT) --HANDLE hConsoleInput, _Out_ PINPUT_RECORD lpBuffer, _In_ DWORD nLength, _Out_ LPDWORD lpNumberOfEventsRead if xReadConsoleInput = -1 then -- Using "ReadConsoleInput" doesnt work, so we try this for the failed explicit "ReadConsoleInputW" xReadConsoleInput = define_c_func( kernel32, "ReadConsoleInputA", {C_ULONG , C_ULONG, C_INT, C_POINTER}, C_INT) end if constant ptrInputRecord = allocate(24) --mem block for a single INPUT_RECORD constant KEY_EVENT=1 --EventType is a key press/release constant MENU_EVENT=8 constant FOCUS_EVENT=16 -- INPUT_RECORD typedef; abs address of record members in ptrInputRecord (ie includes +offset) constant EventType = ptrInputRecord+0 -- KEY_EVENT union; constant bKeyDown=ptrInputRecord+4 constant wRepeatCount=ptrInputRecord+8 constant wVirtualKeyCode=ptrInputRecord+10 constant wVirtualScanCode=ptrInputRecord+12 constant AsciiChar=ptrInputRecord+14 constant dwControlKeyState=ptrInputRecord+16 ------------------ constant xGetStdHandle= define_c_func( kernel32, "GetStdHandle", {C_INT}, C_ULONG) constant hConsoleInput=c_func(xGetStdHandle, {-10} ) -- -10 = STD_INPUT_HANDLE if hConsoleInput = -1 then ?9/0 end if --BOOL SetConsoleTitle(char *lpConsoleTitle) --Kernel32 -- see H:\dl\c\win32\console API.pdf EVENT_CONSOLE_CARET callback event . -- H:\dl\c\win32\WinEvents API.pdf p608 sequence cmd = command_line() sequence argv = cmd[3..$] integer argc = length( argv ), iuret xSetConsoleCtrlHandler = define_c_func(kernel32,"SetConsoleCtrlHandler", {C_POINTER, -- PHANDLER_ROUTINE pHandlerRoutine, // address of handler function C_INT}, -- BOOL fAdd // handler to add or remove C_INT) -- BOOL cbHR = call_back(routine_id("breakFunc")) --{’+’, id} for cdecl if not c_func(xSetConsoleCtrlHandler,{cbHR,1}) then ?9/0 end if --noIup() --test interrupting a console (without iup) loop with async break key. iupStart( argc, argv ) IupSetGlobalFunction("IDLE_ACTION", Icallback("iupIdle")) --iup will call this idle callback so we can flag when iup is idle and reduce polling freq -- IupMainLoop() --blocks this foreground process until user closes GUI loop do --iup event polling loop printf(1, "Press break to debug ") ? idleSleep & hCon & hIup & hCurrent & date() sleep(idleSleep) ----- peek into the keyboard queue for debugger keyboard code sent from external app (NB: we dont use get_key() because onTrace=1 which will cause long delays until the next tracable line occurs and the debugger starts) if not c_func(xGetNumberOfConsoleInputEvents, {hConsoleInput, dwordVar} ) then ?9/0 end if for i = 1 to peek4s(dwordVar) do --read each event (if > 0) {}=getKey() end for ----- hCurrent=c_func(xGetForegroundWindow) if hCurrent != hCon then hIup=hCurrent end if if focusOld then --old window focus before debugger invoked if focusOld = hCon then focusOld=hIup end if --debugging IUP window, so reject hCon if focusOld != hCurrent and time()-debugTime > 0.3 then --old windows not focussed and been some time (stuck in the debugger outside this block) since debugTime updated. {}=c_func(xSetForegroundWindow, {focusOld} ) --focus the old windows focusOld =0 --and finnished refocusing old window, so invalidate (NULL) end if debugTime=time() --update time last in this block end if idleSleep=.01 --do a short sleep for good user interaction speed, unless iup becomes idle and calls iupIdle() which will set a longer sleep to reduce CPU wastage --IupSetAttribute(plot, "REDRAW", NULL) until IupLoopStep() = IUP_CLOSE -- poll and execute next iup event (if any). This will also call iupIdle() if iup gui event queue is idle end loop IupClose() function getKey() --get_key() replacement, that doesnt pause if not c_func(xReadConsoleInput, {hConsoleInput , ptrInputRecord, 1, dwordVar}) then ?9/0 end if --read 1 INPUT_RECORD event, NULL to store # events read. -- *dwordVar stores the address of the win32 provided input record buffer if peek2u(EventType) != KEY_EVENT or not peek(bKeyDown) then ----ignore non-key events or key up events return -1 end if integer ch=peek(AsciiChar) if ch = 3 then --external global 'Alt-Break' Autohotkey script attached to our console and sent break key (useful since we dont loose gui focus) goDebug(1) elsif ch = 0 then --a control key return -1 --ignore end if -- ? ch return ch end function
21. Re: BREAKing into Euphoria
- Posted by abuaf Feb 08, 2022
- 1726 views
- Last edited Feb 26, 2022
The above example is text.ex so it gets a console.
Also above code uses a modified iup.e that can work with more recent iup dll's idle check API. Ie if IupSetGlobalFunction() doesnt exist (original euip bin release ver) then use IupSetFunction(). Otherwise just sub the code above to use IupSetFunction() instead with eiup release.
Posting iup.e context diff so you can patch it yourself (ok Greg diff's are the right thing to do), includes extra consts for key codes in cb_key callback ;
*** iup.e.old Wed Dec 30 14:03:50 2015 --- iup.e Sat Feb 12 19:04:01 2022 *************** *** 53,56 **** --- 53,57 ---- public constant -- function delcarations + xIupSetGlobalFunction = define_c_proc( iup, "+IupSetGlobalFunction", {C_POINTER,C_POINTER}), xIupOpen = define_c_func( iup, "+IupOpen", {C_POINTER,C_POINTER}, C_INT ), xIupClose = define_c_proc( iup, "+IupClose", {} ), *************** *** 255,258 **** --- 256,269 ---- /* Main API */ /************************************************************************/ + --void IupSetGlobalFunction(string name, cbfunc func) + public procedure IupSetGlobalFunction(object name = NULL, atom func) + if sequence(name) then name = allocate_string(name,1) end if + if xIupSetGlobalFunction = -1 then --old iup ver + if c_func( xIupSetFunction, {name,func} ) then end if + return + end if + c_proc( xIupSetGlobalFunction, {name,func} ) + end procedure + --int IupOpen(int *argc, char ***argv); public function IupOpen(atom argc, sequence argv = {}) *************** *** 1676,1677 **** --- 1687,1718 ---- /************************************************************************/ + public function iup_isshift(atom _s) + return peek(_s) = 'S' + end function + public function iup_iscontrol(atom _s) + return peek(_s+1) = 'C' + end function + public function iup_isbutton1(atom _s) + return peek(_s+2) = '1' + end function + public function iup_isbutton2(atom _s) + return peek(_s+3) = '2' + end function + public function iup_isbutton3(atom _s) + return peek(_s+4) = '3' + end function + public function iup_isdouble(atom _s) + return peek(_s+5) = 'D' + end function + public function iup_isalt(atom _s) + return peek(_s+6) = 'A' + end function + public function iup_issys(atom _s) + return peek(_s+7) = 'Y' + end function + public function iup_isbutton4(atom _s) + return peek(_s+8) = '4' + end function + public function iup_isbutton5(atom _s) + return peek(_s+9) = '5' + end function
22. Re: BREAKing into Euphoria
- Posted by ghaberek (admin) Feb 08, 2022
- 1725 views
Wow. So much going on here. Please don't put huge blocks of code in posts. It's too easy to get lost scrolling up-and-down through code. Use Pastey for one-off code sharing or, better yet, put together a project on GitHub and share it here.
While this is a clever idea, Euphoria 4.1 and later has built-in support for a external debuggers. It's not fully documented yet but it's there and I strongly recommend looking at that instead of injecting assembly code. This is the kind of thing we need to see as a use case for fleshing out the external debugger support and its documentation.
- https://github.com/OpenEuphoria/euphoria/blob/master/demo/simple_debug.e
- https://github.com/OpenEuphoria/euphoria/blob/master/include/euphoria/debug/debug.e
Also I need to work on getting a the new site up and running. Very old posts should be locked automatically. We shouldn't be able to reply to threads imported from the mailing list.
-Greg
23. Re: BREAKing into Euphoria
- Posted by katsmeow Feb 08, 2022
- 1651 views
Ah, but I have this enormous program and somewhere, somehow, it is locking up in a loop.
I would use with profile in the olden daze, when it entered the loop to waste it's time, it will rack up huge numbers on the lines it's wasting time in.
24. Re: BREAKing into Euphoria
- Posted by abuaf Feb 26, 2022
- 1529 views
What's the easiest way to contribute the TraceOn functionality patches to euphoria 4.1 source? I had a closer look at "external debugger" api, doesn't seem to provide this functionality to queue a "TraceOn=1" trap out to the debugger at the next traceable code, which is great for pin pointing type checks (or async break key interruption). However the API is useful in this case to get the call stack when the non-traceable 'interruption' occurs, and then locate where the type check failed. Would break key 'interruption' in win32's seperate thread have a valid Euphoria call stack context, or is the thread anonymous without a Euphoria call context? In this case, is it even safe to run euphoria code (it seems to work) in the break handler thread, since I thought Euphoria is not multi-threaded, but one assumes that Euphoria callbacks are 'thread safe' even when they occur part way through a Euphoria byte code instruction.
Wow. So much going on here. Please don't put huge blocks of code in posts. It's too easy to get lost scrolling up-and-down through code. Use Pastey for one-off code sharing or, better yet, put together a project on GitHub and share it here.
While this is a clever idea, Euphoria 4.1 and later has built-in support for a external debuggers. It's not fully documented yet but it's there and I strongly recommend looking at that instead of injecting assembly code. This is the kind of thing we need to see as a use case for fleshing out the external debugger support and its documentation.
- https://github.com/OpenEuphoria/euphoria/blob/master/demo/simple_debug.e
- https://github.com/OpenEuphoria/euphoria/blob/master/include/euphoria/debug/debug.e
Also I need to work on getting a the new site up and running. Very old posts should be locked automatically. We shouldn't be able to reply to threads imported from the mailing list.
-Greg
25. Re: BREAKing into Euphoria
- Posted by ghaberek (admin) Feb 26, 2022
- 1511 views
What's the easiest way to contribute the TraceOn functionality patches to euphoria 4.1 source?
If this is something you want to implement, you are welcome to open a pull request against the Euphoria repo on GitHub. However, I'll warn you now that your method of using assembly code to inject tracing into the code stack is not going to be accepted. It looks quite dangerous and it's not portable to other platforms as it stands. Anything we implement should work on at least x86/x64 Windows and x86/x64/ARM Linux.
I had a closer look at "external debugger" api, doesn't seem to provide this functionality to queue a "TraceOn=1" trap out to the debugger at the next traceable code, which is great for pin pointing type checks (or async break key interruption). However the API is useful in this case to get the call stack when the non-traceable 'interruption' occurs, and then locate where the type check failed.
Well yes, it's an external debugger after all. But it does allow you set step through the code, set break points, and have access to the symbol table. So you could set up a debugger to indefinitely step through the code and checking for key events, and when it gets triggered, grab the call stack and symbol table and go inject the "trace on" op use Euphoria code instead of assembly. This is all hypothetical but I think there's enough implemented to make this possible.
Would break key 'interruption' in win32's seperate thread have a valid Euphoria call stack context, or is the thread anonymous without a Euphoria call context? In this case, is it even safe to run euphoria code (it seems to work) in the break handler thread, since I thought Euphoria is not multi-threaded, but one assumes that Euphoria callbacks are 'thread safe' even when they occur part way through a Euphoria byte code instruction.
From what I can tell, external callbacks drop into their own call stack and execution resumes normally after the callback returns. Everything's still in one thread, but as it stands the interpreter has no idea where it left off before the callback was triggered. But, there's already code built into the backend to handle check_break() so we might be able to utilize that to insert a "trace on" op as necessary.
I plan to look more at the debugging features later, but right now my priorities are memstruct and a handful of standard library features like hashes and compression.
-Greg
26. Re: BREAKing into Euphoria
- Posted by ghaberek (admin) Feb 26, 2022
- 1478 views
- Last edited Feb 28, 2022
I plan to look more at the debugging features later
So much for that, I already figured it out. Turns out it was pretty easy, but I'm still not sure how safe this is. The SIGINT (Ctrl+C) signal interrupt handler is configured in InitExecute(). The first step was to borrow some code from trace_command() and plop that into INT_Handler().
// be_rterror.c:1668 void INT_Handler(int sig_no) /* control-c, control-break */ { UNUSED(sig_no); if (trace_enabled) { /* start tracing */ signal(SIGINT, INT_Handler); TraceOn = TRUE; color_trace = TRUE; #ifdef _WIN32 show_console(); #endif return; } ...
This worked great... when with trace was enabled. Turns out trace_enabled is always set to TRUE and the backend relies on the frontend to emit the necessary tracing ops. So my trick would act like allow_break(FALSE) without trace enabled. My solution was to grab trace_enabled from the parser section for with/without trace and pass that through to the backend.
-- global.e:617 export integer trace_enabled = FALSE
Edit: I made a small change here. Originally, trace_enabled would turn on-and-off depending on the last with/without trace statement. That's not going to work since tracing would be ignored if the last check was without trace, and any omitted tracing ops would be ignored. This makes trace_enabled "sticky" in that it will only ever turn on and won't ever turn off.
-- parser.e:4529 ... elsif equal(option, "trace") then if not BIND then OpTrace = on_off -- trace_enabled = on_off if on_off then trace_enabled = TRUE end if end if ...
-- backend.e:285 machine_proc(65, { ... trace_lines, trace_enabled, $ })
// be_machine.c:2842 object start_backend(object x) /* called by Euphoria-written front-end to run the back-end * * x is {symtab, topcode, subcode, names, line_table, miscellaneous } */ { ... trace_lines = get_pos_int(w, *(x_ptr->base+16)); trace_enabled = get_pos_int(w, *(x_ptr->base+17)); ...
That seems to do the trick. I made a simple little program that goes into a loop for 60 seconds and when I press Ctrl+C it jumps into the trace screen. Pressing q resumes execution until I press Ctrl+C again to jump back to trace, and pressing Q resumes execution and disables trace. Pressing Ctrl+C again from there stops the program normally.
include std/os.e with trace atom t0 = 0 atom t1 = time() atom t2 = t1 + 60 while t1 < t2 do if t1 > t0 then t0 = t1 ? t0 end if sleep(rand(10)/100) t1 = time() end while
This is a pretty neat feature to have, but I'll say it again loudly: I don't know enough about the interpreter yet to say how safe this is. Your app might crash. Weird things might happen.
-Greg
27. Re: BREAKing into Euphoria
- Posted by abuaf Feb 26, 2022
- 1482 views
- Last edited Feb 27, 2022
[quote ghaberek]
What's the easiest way to contribute the TraceOn functionality patches to euphoria 4.1 source?
If this is something you want to implement, you are welcome to open a pull request against the Euphoria repo on GitHub. However, I'll warn you now that your method of using assembly code to inject tracing into the code stack is not going to be accepted. It looks quite dangerous and it's not portable to other platforms as it stands. Anything we implement should work on at least x86/x64 Windows and x86/x64/ARM Linux.
See my quote below. This is *not* how my code works. Essentially in the eu source, trace(1) sets the C var "TraceOn=1;" . This then triggers the debugger to be invoked when the existing "trace on" eu byte code is next hit (what i referred to as "pop up the debugger when the next trace'able code is encounterred"). My code patches the C var "TraceOn=1;" in the eu binary in virtual memory (because there's no api exposing access to this). You can then refer to my comments to provide this access via trivial additions to allow_break() in interpreter's C source; this is then the implementaion - nothing to do with the problematic mission of patching eu byte code; i dont think there is any way to locate at runtime where in the bytecode to patch and how to revector the old byte code to allow continuity. Me thinks I'd have to parse eu's symbolic debug stream wind forward to the next eu instruction bytecode address falling on the next eu source line and patch that ... ugly, and not needed, this is not how eu's debugger works; basically the bytecode has the 'trace on' opcode preceeding each new eu source line (keeping in mind each line corresponds to many byte codes which would be un-atomic otherwise), that the interpreter then polls the C "TraceOn" var and invokes the debugger if true, so all the machinery to internally (via a API to the eu app to) trigger the debugger at an abitrary moment is there; which my code does without an exposed API.
Anyone who wants to wrap their head around my code, pls read my comments very carefully. Anyway i reimplemented it without ASM with eu equivalent and some fluff, but the 'hack' is the same methodology.
I had a closer look at "external debugger" api, doesn't seem to provide this functionality to queue a "TraceOn=1" trap out to the debugger at the next traceable code, which is great for pin
28. Re: BREAKing into Euphoria
- Posted by ghaberek (admin) Feb 26, 2022
- 1478 views
See my quote below. This is *not* how my code works. Essentially in the eu source, trace(1) sets the C var "TraceOn=1;" . This then triggers the debugger to be invoked when the existing "trace on" eu byte code is next hit (what i referred to as "pop up the debugger when the next trace'able code is encounterred"). My code patches the C var "TraceOn=1;" in the eu binary in virtual memory (because there's no api access for this). You can then refer to my comments to provide this access via trivial additions to allow_break() in interpreter's C source; this is then the implementaion - nothing to do with the problematic mission of patching eu byte code; i dont think there is any way to locate at runtime where in the bytecode to patch and how to revector the old byte code to allow continuity. Me thinks I'd have to parse eu's symbolic debug stream wind forward to the next eu instruction bytecode address and patch that ... ugly, and not needed, this is not how eu's debugger works; basically the bytecode has the 'trace on' opcode, that the interpreter then polls the C "TraceOn" var and invokes the debugger if true, so all the machinery to internally (via API) trigger the debugger at any abitrary moment is there; which my code does without an API.
Anyone who wants to wrap their head around my code, pls read my comments very carefully. Anyway i reimplemented it without ASM with eu equivalent and some fluff, but the 'hack' is the same methodology.
Yes you're right. Sorry I missed that. But even without assembly code, poking directly at random memory addresses is bad. The correct way to do this is either in via the backend, as I've implemented above, or via the external debugger API, which I'll work on adapting to better accommodate this feature. I was busy digging into this and we both ended up replying at the same time. Hopefully what I posted above shows some progress towards this feature.
-Greg
29. Re: BREAKing into Euphoria
- Posted by abuaf Feb 27, 2022
- 1454 views
Yes you're right. Sorry I missed that. But even without assembly code, poking directly at random memory addresses is bad. The correct way to do this is either in via the backend, as I've implemented above, or via the external debugger API, which I'll work on adapting -Greg
"I've (you've) implemented"??? Did you already implement this, and has it made it to github since that would save me time? If you implement it in the backend, then there's no need to add a debug API for it. I find this backend method useful for libraries; often you don't want to pop out to the debugger in the library (which has been tested to work) - the problem is with the user app (same for type checks). What you want is to pop up the debugger in the user's code ("with trace" section), and keep the library / type check "without trace". This reflects an elegance of euphoria; that the user has control of the scope of debuggability, only now it can be queued in "without trace" sections.
I've been having some thoughts about the debugger API;
- Does it extend euphoria's internal debugger functionality or does the API replace that functionality? Obviously very unattractive and wasteful to recreate the existing debugger's functionality.
- My pet annoyance is the debugger's ignorance of string sequences and thumping them out as interspersed numbers. Naturally we don't want to explicity run-time mark sequences as 'strings'. But we could modify the interpreter to assign some pointer part of the string to a string memory address range, and have an API to query the string-edness of a sequence.
- Irrespective of above implicit string marking, a 's' command to print out a sequence as a string ???
- Quick hack; a 'string' type check, that not only checks that all sequence elements are integers [0..255] , but makes a simplified CRC of the sequence and stores that for later so the debugger can match against it and print appropriately. The aim here is to fingerprint strings on assignment. Maybe there's a faster backend method to finger print sequences based on their internal pointers.
- The string printing issue is part of a bigger issue on sequence printing in general. In the end only the eu app can pretty print a complex sequence according to appropriate user preference. Ie you dont want a detailed page long full dump of a complex sequence. So i would suggest a eu callback (in "without trace" code to prevent recursion) that would take a sequence as input, and return a string for the debugger to print. This would apply to the new debugger 's' command, so if the user really wants a full dump, use the usual '?' command.
- Just to save keystrokes, and do as gdb does, dont use ? but 'p' to print a var.
- Finally as an alternative to slow type check fingerprinting on var write, instead a debugger output reprint callback. Eg on app init, a table of special var names is created. Debugger adds variables to debug display. Then it makes the 'reprint' callback, which takes as arg a string of the debug display area. Then the callback can consult the special var name list and rewrite values of matching variables, and return that. Finally the debugger displays what is returned. I think this is the least obtrusive, easiest way, without adding any significant runtime changes, and without resorting to a new debugger. How to support debug printing of seq[4] ? We first let the debugger accept the array subscript as part of the variable name to print. No value is printed because the debugger is too dumb to do that. Instead the reprint callback handle this complexity, even accepting a variable as a sequence subscript rather than going full parser. All this is in the name of keeping the runtime lean (small) and mean (fast). Even the few KB of this reprint euphoria can can be cancelled with a ifdef .
Any better ideas? In the end i believe the debugger should get relatively small attention since its not the main operation of euphoria (an interpreter/translater). Like most users (maintainers?) my objective is not to spend years supporting a hobby horse, i just want to get some well defined programs done. I'm willing to spend a bit of time to make that done more comfortably. Too utilitarian? Each to his own.
30. Re: BREAKing into Euphoria
- Posted by abuaf Feb 27, 2022
- 1477 views
I plan to look more at the debugging features later
So much for that, I already figured it out. Turns out it was pretty easy, but I'm still not sure how safe this is. The SIGINT (Ctrl+C) signal interrupt handler is configured in InitExecute(). The first step was to borrow some code from trace_command() and plop that into INT_Handler().
// be_rterror.c:1668 void INT_Handler(int sig_no) /* control-c, control-break */ { UNUSED(sig_no); if (trace_enabled) { /* start tracing */ signal(SIGINT, INT_Handler); TraceOn = TRUE; color_trace = TRUE; #ifdef _WIN32 show_console(); #endif return; } ...
This worked great... when with trace was enabled. Turns out trace_enabled is always set to TRUE and the backend relies on the frontend to emit the necessary tracing ops. So my trick would act like allow_break(FALSE) without trace enabled.
I'm not sure why you have conditioned TraceOn with " if (trace_enabled) {" . Does then mean Ctrl-C gets ignored when in "without trace" code sections? If this is the case, then this defeats the objective of a debugger allowing interruption of the program. What is desired is defferal (queuing) of the interruption until the next "with trace" section. Can you test this by adding a loop in "without trace" section and pressing break while there in the code below?
include std/os.e without trace function untracable() puts(1, "Untraceable press break now") sleep(4) end function with trace atom t0 = 0 atom t1 = time() atom t2 = t1 + 60 while t1 < t2 do if t1 > t0 then t0 = t1 ? t0 end if sleep(rand(10)/100) t1 = time() untracable() end while
This is a pretty neat feature to have, but I'll say it again loudly: I don't know enough about the interpreter yet to say how safe this is. Your app might crash. Weird things might happen.
-Greg
I modified eu 3.1 DOS source to do this about 10 years ago on a big program, never any problem. In a way it cannot cause a problem, since it doesnt modify eu byte code execution in any way other than what it originally is designed to do. The break-key win32 event callback occurs in the backend; this already happens and is compeletely normal and C thread safe since it happens in C. The issue is "is it safe to run a eu callback from a win32 event thread callback (in the backend)?" To really test this, need to run a eu CRC routine while a high-frequency win32 multimedia timer event is firing. The aim is to test arbitrary eu byte code execution interruption - even during partial execution of a byte code - because that is what will happen. I suspect this is not safe, unless the eu callback dispatch code has special byte code thread saving. I havent examined the code for many years, but 99% doubt it has any thread saving. But you never know. This is why i originally used ASM to sidestep this uncertainty. Alternatively we can ask "Is there any other eu libs/code etc that handles win32 callback events - eg win32 sound multimedia playback callback from a buffer? Eg EuSDL2-master but i think the sound is fully handled in the C DLL. If a euphoria sound buffer fill callback supported then this can be tested. IUP must be doing this because as it uses the winProc gui callback originating in the DLL and then propogating to eu callbacks. But maybe they cheat and queue this type of callback, and do polling in euphoria code to dispatch the eu callback safely from euphoria. Someone want to check all this?
31. Re: BREAKing into Euphoria
- Posted by abuaf Feb 27, 2022
- 1441 views
Greg, just wondering what you like to do about API to set TraceOn=1/color_trace=1 from eu code (eg allow_break(2)) so eu apps can trigger the debugger from "without trace" sections. I use this in type check's to pop up the debugger after the variable assignment so i know exactly what variable and place in my app's code has caused the type failure.
32. Re: BREAKing into Euphoria
- Posted by ghaberek (admin) Feb 28, 2022
- 1428 views
"I've (you've) implemented"???
LOL well I meant, you designed it and I implemented it. If we release with this feature you'll surely be credited for the contribution.
Did you already implement this, and has it made it to github since that would save me time?
No not yet, but it's a relatively small change so if I can determine it's safe to use then I can push the change to the repo soon.
If you implement it in the backend, then there's no need to add a debug API for it.
For this? No. You're right that there's no need to have an external debugger trigger the internal trace screen; it should implement its own trace-like interface.
I find this backend method useful for libraries; often you don't want to pop out to the debugger in the library (which has been tested to work) - the problem is with the userapp (same for type checks). What you want is to pop up the debugger in the user's code ("with trace" section), and keep the library / type check "without trace". This reflects an elegance of euphoria; that the user has control of the scope of debuggability, only now it can be queued in "without trace" sections.
Right, and that got me thinking about complex with/without trace scenarios. I made a small addition to the changes I listed above in parser.e to help with this.
I've been having some thoughts about the debugger API;
FYI Matt wrote the external debugger API for Euphoria 4.1 several years ago. I think I've got a pretty good handle on how it works and I'd very much like to improve on it.
- Does it extend euphoria's internal debugger functionality or does the API replace that functionality? Obviously very unattractive and wasteful to recreate the existing debugger's functionality.
I think the best answer is "both?" What the external debugger API does is allow you to add hooks into some existing functions that already exist in the backend. For example, when the backend encounters a trace op, it calls DebugScreen() to flip over to the trace screen you see now. You can implement your own DebugScreen() to be called instead, and that can be whatever you want. From there, you can query the symbol table and the call stack, etc. You can also inject trace statements to act as break points. I'm hoping to use this to implement full-on external debugging in my vscode-extension extension.
- My pet annoyance is the debugger's ignorance of string sequences and thumping them out as interspersed numbers. Naturally we don't want to explicity run-time mark sequences as 'strings'. But we could modify the interpreter to assign some pointer part of the string to a string memory address range, and have an API to query the string-edness of a sequence.
Well, this is its own problem altogether and not specific to the trace screen. Your ex.err dumps are going to be full of non-string sequences as well. I know in Phix, Pete has implemented strings as their own dedicated type. I think that would be more complicated with Euphoria but I'm exploring options of expanding the type system to be a little "smarter" than it is today. In the meantime, I'm hoping to at least improve the "pretty print" functionality that's used in trace and ex.err to prefer string-like output, and possibly have a way to disable this. (Something like without pretty_errors)
- Irrespective of above implicit string marking, a 's' command to print out a sequence as a string ???
That's a good idea. I'll look into that along with your 'p' suggestion below.
(Long thread; replies continued below.)
-Greg
33. Re: BREAKing into Euphoria
- Posted by ghaberek (admin) Feb 28, 2022
- 1419 views
- Quick hack; a 'string' type check, that not only checks that all sequence elements are integers [0..255], but makes a simplified CRC of the sequence and stores that for later so the debugger can match against it and print appropriately. The aim here is to fingerprint strings on assignment. Maybe there's a faster backend method to finger print sequences based on their internal pointers.
The trace screen is pretty dumb in what it knows about types. It might be possible to store some additional information in the symbol table that it could use. I've been thinking about this as part of my ideas for expanding the type system that I mentioned above. But this next feature might be what helps the most:
- The string printing issue is part of a bigger issue on sequence printing in general. In the end only the eu app can pretty print a complex sequence according to appropriate user preference. Ie you dont want a detailed page long full dump of a complex sequence. So i would suggest a eu callback (in "without trace" code to prevent recursion) that would take a sequence as input, and return a string for the debugger to print. This would apply to the new debugger 's' command, so if the user really wants a full dump, use the usual '?' command.
Building on that, I just had an idea: just bake this into the interpreter with something like a "string_routine()" similar to Python's object.__str__() or .NET's Object.ToString(). Basically, any "true" object (atom or sequence) could have a routine attached just like how we're doing delete_routine() today (a pointer/routine_id to a "cleanup" function). The default implementation could just print atoms as "%d" and sequences as {65'A',66'B',...} like it does today. But, and this is where it gets clever: sequences declared with quotes could have a different routine assigned that tries to print the string directly, and it could still fallback to the default function if it encounters non-string data. And, by exposing this to the frontend like delete_routine(), users can implement their own custom handlers. That would be helpful for stuff like memory addresses returned from allocate(), which would be better printed as "0x%08x". I will look into implementing this but it won't be for a while.
- Just to save keystrokes, and do as gdb does, dont use ? but 'p' to print a var.
I think '?' was used originally because it's the built-in "quick print" operator. It's also a query like, "what is this variable?" It's possible to use almost any key for this purpose so we could use both and display "?/p" in the help area instead.
- Finally as an alternative to slow type check fingerprinting on var write, instead a debugger output reprint callback. Eg on app init, a table of special var names is created. Debugger adds variables to debug display. Then it makes the 'reprint' callback, which takes as arg a string of the debug display area. Then the callback can consult the special var name list and rewrite values of matching variables, and return that.
I think this part might be best handled by adding the string_routine() I mentioned above.
- Finally the debugger displays what is returned. I think this is the least obtrusive, easiest way, without adding any significant runtime changes, and without resorting to a new debugger. How to support debug printing of seq[4] ? We first let the debugger accept the array subscript as part of the variable name to print. No value is printed because the debugger is too dumb to do that. Instead the reprint callback handle this complexity, even accepting a variable as a sequence subscript rather than going full parser. All this is in the name of keeping the runtime lean (small) and mean (fast). Even the few KB of this reprint euphoria can can be cancelled with a ifdef .
Allowing to query sequence subscripts would be a nice feature. I'll look into that as well.
Any better ideas?
That's quite a few! This kind of feedback is very good though and I'm happy to try accommodating whatever we can make fit.
In the end i believe the debugger should get relatively small attention since its not the main operation of euphoria (an interpreter/translater). Like most users (maintainers?) my objective is not to spend years supporting a hobby horse, i just want to get some well defined programs done. I'm willing to spend a bit of time to make that done more comfortably. Too utilitarian? Each to his own.
I think good debugging tools are essential to a good language and to writing good software. As Brian Kernighan once said:
-Greg