1. BREAKing into Euphoria

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

new topic     » topic index » view message » categorize

2. Re: BREAKing into Euphoria

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. smile

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

new topic     » goto parent     » topic index » view message » categorize

3. Re: BREAKing into Euphoria

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. smile
> 
> 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

new topic     » goto parent     » topic index » view message » categorize

4. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

5. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

6. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

7. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

8. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

9. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

10. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

11. Re: BREAKing into Euphoria

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."

new topic     » goto parent     » topic index » view message » categorize

12. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

13. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

14. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

15. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

16. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

17. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

18. Re: BREAKing into Euphoria

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

new topic     » goto parent     » topic index » view message » categorize

19. Re: BREAKing into Euphoria

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 ) 
 
 
Andy Drummond <andy at k?strel?ele.com> said...

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 

new topic     » goto parent     » topic index » view message » categorize

20. Re: BREAKing into Euphoria

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 
 
new topic     » goto parent     » topic index » view message » categorize

21. Re: BREAKing into Euphoria

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 
 

new topic     » goto parent     » topic index » view message » categorize

22. Re: BREAKing into Euphoria

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.

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

new topic     » goto parent     » topic index » view message » categorize

23. Re: BREAKing into Euphoria

Andy Drummond <andy at kestreltele.co?> said...

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.

new topic     » goto parent     » topic index » view message » categorize

24. Re: BREAKing into Euphoria

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.

ghaberek said...

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.

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

new topic     » goto parent     » topic index » view message » categorize

25. Re: BREAKing into Euphoria

abuaf said...

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.

abuaf said...

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.

abuaf said...

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

new topic     » goto parent     » topic index » view message » categorize

26. Re: BREAKing into Euphoria

ghaberek said...

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

new topic     » goto parent     » topic index » view message » categorize

27. Re: BREAKing into Euphoria

[quote ghaberek]

abuaf said...

What's the easiest way to contribute the TraceOn functionality patches to euphoria 4.1 source?

ghaberek said...

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.

abuaf said...

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

new topic     » goto parent     » topic index » view message » categorize

28. Re: BREAKing into Euphoria

abuaf said...

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

new topic     » goto parent     » topic index » view message » categorize

29. Re: BREAKing into Euphoria

ghaberek said...

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.

new topic     » goto parent     » topic index » view message » categorize

30. Re: BREAKing into Euphoria

ghaberek said...
ghaberek said...

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 
ghaberek said...

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?

new topic     » goto parent     » topic index » view message » categorize

31. Re: BREAKing into Euphoria

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.

new topic     » goto parent     » topic index » view message » categorize

32. Re: BREAKing into Euphoria

abuaf said...

"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.

abuaf said...

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.

abuaf said...

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.

abuaf said...

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.

abuaf said...

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.

abuaf said...
  • 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.

abuaf said...
  • 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)

abuaf said...
  • 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

new topic     » goto parent     » topic index » view message » categorize

33. Re: BREAKing into Euphoria

abuaf said...
  • 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:

abuaf said...
  • 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.

abuaf said...
  • 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.

abuaf said...
  • 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.

abuaf said...
  • 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.

abuaf said...

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.

abuaf said...

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:

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

-Greg

new topic     » goto parent     » topic index » view message » categorize

Search



Quick Links

User menu

Not signed in.

Misc Menu