Shop OBEX P1 Docs P2 Docs Learn Events
Call spin routine by pointer? — Parallax Forums

Call spin routine by pointer?

Dennis FerronDennis Ferron Posts: 480
edited 2009-04-14 08:12 in Propeller 1
Is it possible to call a spin routine via a pointer? Like how you pass the name of a routine to cognew.

I was thinking it would be useful to have an object that executes serial commands, and to make it generic, it would take a command number and a pointer to a spin routine to execute when that command number comes in. Something like:


OBJ

  ser_cmd : "Serial_Command"

PUB Demo

  ser_cmd.start(31, 30, 19200)

  ser_cmd.add(1, @ActionOne)
  ser_cmd.add(2, @ActionTwo)

  ' Wait for commands to come in
  repeat
    ser_cmd.DoCmd

PRI ActionOne

  ' Do stuff when a "1" comes in

PRI ActionTwo

  ' Do other stuff when a "2" comes in


Comments

  • Bill DrummondBill Drummond Posts: 54
    edited 2009-04-12 03:58
    PRI DoProc(Do_stuff)
     Case D0_Stuff
      1: 1_Stuff
      2: 2_Stuff
      other: Defalt_Stuff
    
  • jazzedjazzed Posts: 11,803
    edited 2009-04-12 04:41
    
    CON
      ' Object Overload function pointer call example.
      ' Takes advantage of Spin Compiler's lack of object array boundary checking.
      
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
        
    OBJ
      ob  : "object"  ' ob[noparse][[/noparse]0]
      ob1    : "object1" ' ob
      ob2    : "object2" ' ob
      ob3    : "object3" ' ob
    
    OBJ fd   : "FullDuplexSerial"
    
    PUB main | n
    
      fd.start(31,30,0,115200)
      fd.rx ' wait for user input
      fd.str(string($d,"Object Overload Function Pointer Demo",$d))
        
      repeat n from 0 to 3
        fd.dec(action(n)) ' print numbers returned by object function 
        fd.tx(" ")
    
      repeat
    
    PUB action(index)
      return ob[noparse][[/noparse]index].function
        
    
    



    ' object.spin
    
    pub function
      return 0
    
    
    



    ' object1.spin
    
    pub function
      return 1
    
    
    



    ' object2.spin
    
    pub function
      return 2
    
    
    



    ' object3.spin
    
    pub function
      return 3
    
    
    



    Cheers

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    --Steve


    Propalyzer: Propeller PC Logic Analyzer
    http://forums.parallax.com/showthread.php?p=788230
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2009-04-12 04:52
    Dennis,

    It should be noted that Steve's solution is not a supported feature of the language, despite its irresistable cleverness. Just make sure that your argument count among the various object methods is the same, as must be their relative position (definition order) within each object.

    -Phil
  • Dennis FerronDennis Ferron Posts: 480
    edited 2009-04-12 06:25
    Bill, your example is exactly the code I'm trying NOT to have. eyes.gif The point is I won't even know ahead of time what goes into the CASE statement. The functionality is intended to be provided by the user of the object.

    Steve's solution is remarkable (I didn't realize you could have arrays of objects in Spin!) but has limitations as Phil points out. You could make it work but not for multiple subroutines in the same object.

    An old x86 trick involves pushing a desired address onto the stack, and then executing a ret instruction without a corresponding call first. A ret is a just a pop and a jump, so you can trick the CPU into jumping to an arbitrary address when it executes the return. Maybe this could be accomplished with a similar stack hack? Like this:

    
    VAR
      long FunctPtr[noparse][[/noparse]10]
    
      long OldRetAddr
    
    PUB Demo
      FunctPtr := @DoStuff
      DoCmd(1)
    
    PRI DoCmd(cmd)
      OldRetAddr := long[noparse][[/noparse]GetReturnAddr]
      JumpTo(FunctPtr[noparse][[/noparse]cmd])
      
    PRI JumpTo(ptr) 
      ' Somehow determine where in the stack our return address was stored
      ' and modify the location so that we return to ptr instead of our old value.
      long[noparse][[/noparse]GetReturnAddr] := ptr
    
      ' Now this return takes us to DoStuff, not to the end of DoCmd
      return
    
    PRI GetReturnAddr 
      '  f***ing magic!
    
    PRI DoStuff
      ' do stuff
    
      ' Important:  if we return normally at this point, we're dead.  Probably.
      ' Have to put the stack back in order and pretend this never happened.
      FixStack
      JumpTo(OldRetAddr)
    
    
    



    In principle this could work if GetReturnAddr works. It's similar to the kind of thing malicious code has to do to actually exploit a buffer overrun vulnerability in an app. Of course there's a lot of caveats to this too - the presence and number of function arguments will affect things, and you have to clean up the stack when you're done.
  • jazzedjazzed Posts: 11,803
    edited 2009-04-12 07:34
    Someone else stumbled upon the object array bounds situation; I just remembered it [noparse]:)[/noparse]
    Unfortunately that's all we got for now. I figured from your first post you knew the dangers of having such a "rope" [noparse]:)[/noparse]

    I believe the address of "result" is the current stack pointer ....
    The current function address can be obtained with a statement like: addr := word[noparse][[/noparse]@result-2].
    The return address would be: retaddr := word[noparse][[/noparse]@result-4] .

    You may find it useful to look at the spin interpreter for digging in ... it is posted on the forums:
    http://forums.parallax.com/forums/default.aspx?f=25&m=252691

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    --Steve


    Propalyzer: Propeller PC Logic Analyzer
    http://forums.parallax.com/showthread.php?p=788230
  • hippyhippy Posts: 1,981
    edited 2009-04-12 09:59
    Dennis Ferron said...
    An old x86 trick involves pushing a desired address onto the stack, and then executing a ret instruction without a corresponding call first ... Maybe this could be accomplished with a similar stack hack?

    Unfortunately that simple hack will not work in Spin as calls are not performed to the address of the target method but are numeric indexes into a jump table, and the jump table also contains the equivalent of 'stack frame' adjustments which need to be executed before the call.

    There are two alternative hacks; patching the executable bytecode at run-time ( not good for code simultaneously executed by two cogs ) or patching the jump table ( copying required data into a dummy entry ). Neither are simple and require an understanding of the object code layout. There should be additional information in previous forum posts.
  • Beau SchwabeBeau Schwabe Posts: 6,568
    edited 2009-04-12 13:17
    Dennis Ferron,

    I'm not sure if this is along the lines of what you are looking for, but this example allows an Assembly program to run Spin Code by using a Spin reference pointer and either Exit the Assembly code and return back to Spin, or return back to the Assembly code after the Spin code has completed. By returning back to the Assembly code you could check for other command values, and set it up to function like your original example, but I guess I don't see how that's entirely different than using the Spin CASE command.



    [s][b]CON[/b]     ''General Constants for Propeller Setup
      [b]_CLKMODE[/b] = [b]XTAL1[/b] + [b]PLL16X[/b]
      [b]_XINFREQ[/b] = 5_000_000
    
    
    [b]OBJ[/b]     ''Setup Object references that make this demo work
        Ser       : "FullDuplexSerial"
    
    
    [b]PUB[/b] Main_Program
    
        Ser.start(31, 30, 0, 19200)     '' Initialize serial communication to the PC
    
        [b]cognew[/b](@ASM_SpinRun,DEMO_Test)
        
    
    [b]PUB[/b] DEMO_Test
        ser.tx(0)                                           ''Clear Display
        [b]repeat[/b] 500
          ser.[b]str[/b]([b]string[/b]("[b]Test[/b]     "))                      ''Print "Test" 500 times
    
    [b]DAT[/b]
    
    ASM_SpinRun
            [b]mov[/b]   t1,       [b]par[/b]
            [b]rdlong[/b] t1,      t1
            [b]jmpret[/b] t1,      #WeAreBackFromSpin      ''Call SpinCode from Assembly and return
    '        jmp    t1       ''Call SpinCode from Assembly and don't come back ; Stops Assembly COG
    
    WeAreBackFromSpin
            [b]mov[/b]             [b]outa[/b],     mask          ''Make ALL the LEDs HIGH
            [b]mov[/b]             [b]dira[/b],     mask          ''Set ALL the LEDs on the demo board as output
    
            [b]add[/b]             OneSecond,              [b]cnt[/b]    ''Wait for 1 second so we can see the LED's
            [b]waitcnt[/b]         OneSecond,              #0       
    
            [b]cogid[/b]           t1                      ''Stop COG
            [b]cogstop[/b]         t1 
             
    
    t1      [b]long[/b]  0
    mask    [b]long[/b]  %00000000_11111111_00000000_00000000
    OneSecond     [b]long[/b]      80_000_000
    [/s]
    
    

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Beau Schwabe

    IC Layout Engineer
    Parallax, Inc.

    Post Edited (Beau Schwabe (Parallax)) : 4/13/2009 5:22:40 AM GMT
  • Dennis FerronDennis Ferron Posts: 480
    edited 2009-04-12 17:17
    Thanks Beau, that sounds close to what I need, although I don't quite understand how it works in detail - doesn't the assembly cog need to load up a Spin interpreter (either in itself or in a 2nd cog) and tell it to execute the bytecode? Assembly language jumps don't jump to hub ram, they jump to a cog register. If you just tried to jump to the location of the Spin code - let's say it's at location $4321, it expect you'll just execute arbitrary uninitialized cog memory at register ($4321 modulus $200), or $121.

    The difficulty is that we're not just talking about making a cog do something, where assembly lets us do whatever we want; we have to make the Spin interpreter *program* running in its own cog do something it wasn't designed to do.

    However, the *intent* of your example is probably right in how it would have to work. Of all the caveats that the other approaches have, the caveat that the executed command runs in a separate cog is probably the least onerous. When a command comes in you launch an assembly cog that in turn loads a coginit/cognew/cogstart whatever, and wait for the command to finish. Does the cog automatically shut down when it comes to the end of a Spin program? Because then you might be able to monitor the launched cog and know the command is done when the cog halts.

    In regards to how it's different than the Spin CASE command, let me make an analogy. How is:

    VAR
       long x
    
    PUB Demo
      SetValue(@x)
    
    PUB SetValue(ptr)
      long[noparse][[/noparse]ptr] := 10
    
    
    



    Different from:

    VAR
       long x
    
    PUB Demo
      SetValue
    
    PUB SetValue
      x := 10
    
    



    Both "do" the same thing, but in the first example SetValue could be in one object and used by a completely different object, whereas the second example has to "know" about the variable x and can't be used to say, set the value of Y.

    This is the same way: in a CASE statement you put the code you want executed in their literally like in the second example above; I want the user to be able to change things at runtime like the first example above. I can't stress enough that we don't even know ahead of time what that code will be - that's to be set by the user of the command processor. You can't use a CASE statement if the code to execute in each case doesn't even exist yet when the object containing the CASE command is written.
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2009-04-12 18:28
    Beau,

    I'm very confused by your example. The jmpret t1, #WeAreBackFromSpin simply jumps to #WeAreBackFromSpin and deposits the return address in t1, overwriting the source field. I can't see any mechanism there for executing a Spin routine. I believe that DEMO_Test is getting executed before the cognew is executed (i.e. the value it returns get put into PAR).

    -Phil
  • MagIO2MagIO2 Posts: 2,243
    edited 2009-04-12 21:16
    Hi Phil,

    this is magic. I'm as confused as you, but he seems to tell us the truth. I added some lines of code:
    
    
    VAR
      long i
     
    PUB Main_Program
      Ser.start( 31, 30, 0, 57600 )
      Ser.rx
      dira[noparse][[/noparse]3]:=1
      outa[noparse][[/noparse]3]:=1
      cognew(@ASM_SpinRun,DEMO_Test)
      repeat
        !outa[noparse][[/noparse]3]
        waitcnt( clkfreq/4 +cnt)
        
    PUB doinnothin
        dira[noparse][[/noparse]4]:=1
        outa[noparse][[/noparse]4]:=1
        
    PUB DEMO_Test
        ser.tx(0)                                           ''Clear Display
        repeat i from 1 to 1000
          ser.str(string("Test "))                      ''Print "Test" 500 times
          ser.dec( i )
          ser.str(string("   "))
    
     
    

    First of all the program waits until I hit any key in the terminal.
    The original SPIN COG then sets output pin 3 (which is a LED output on my board). And yes, the LED is on. After the cognew I added some lines which make the LED blink. This should normally happen very fast. But here it waits until the DEMO_Test has been executed. Then the LEDs set by the PASM blink one time and the LED driven by SPIN starts blinking.
    I did some more changes and let the PASM code blink the LEDs endlessly instead of doing COGSTOP. And the blink.
    Conclusion:
    By magic the ...... got it!

    Last test showed it. I made a comment out of the jmpret. If this would be the code that starts the SPIN, then now the test-strings should not be send. But they are.
    What happens is that SPIN itself first tries to solve all parameters. You can have numerical expressions in each of the cognew parameters. These will be solved during runtime. The second parameter is a function. Solving a function means to call it and pass the return-value of the function as parameter. (Phil, you are right)
    Beau, I really was willing to believe you, but in the end the program only gave you the look and feel of what you want, but it works differently.
    It's really amazing how tricky some bugs are. It really looks like working as desired after several changes of the code.

    Post Edited (MagIO2) : 4/12/2009 9:22:47 PM GMT
  • MagIO2MagIO2 Posts: 2,243
    edited 2009-04-12 21:58
    Back to the original problem. I think it is possible with self modifying code. But as pointers to literals inside a PUB or PRI section are not available it's definitely a piece of code which is a bit difficult for average use. The idea is the following:

    SPIN already has a table with the jump-adresses of all PUB and PRI functions. A call has a bytecode opcode and as parameter an index from that table. So you have to identify the call and place another index as parameter.

    rough scetch of the idea:
    PUB call( index ) | i
      if call_adr==0
        ' search the pattern $AABBAABB in memory
        ' you have to do that bytewise, because longs don't need to be long aligned inside the bytecode
        ' end_of_dat and begin_of_var should be defined - gess where
        repeat whateveradress from @end_of_dat to @begin_of_var
          ' ** missing: do the pattern search and stop if found
        call_adr := whateveradress + offset to next SPIN instructions parameter (propably 5)
      else
        byte[noparse][[/noparse] call_adr+1 ] := index
      i:=$AABBAABB            ' this is needed to find the call inside the bytecode
      OtherMethod             ' this originally calls one of the messages in the index
      
    

    This code has not been tested.

    When calling·'call' first time, it searches for a pattern in memory. As the values of an assignment are really stored within the SPIN code, such an assignment can be used to·locate·code adress in memory. Of course this could be put in a different function.
    Once the adress has been found, the parameter of the call (opcode $05) only has to be replaced.

    The index simply has the same order than the functions have in the SPIN-file.


    Post Edited (MagIO2) : 4/12/2009 10:24:42 PM GMT
  • stevenmess2004stevenmess2004 Posts: 1,102
    edited 2009-04-13 01:18
    MagIO2 said...
    But as pointers to literals inside a PUB or PRI section are not available
    What about the "string()" method? That gives a pointer but you'd have to check exactly what it points too in relation to other things. (The listings from BST or Homespun will help). Another option is to uses the "@@" operator which will give you the start of the object table at runtime and from there it isn't too hard to modify the object/method table. The other option to find the end of the object table is to use the address of the first DAT variable which is directly after the address to the last PRI method.
  • Beau SchwabeBeau Schwabe Posts: 6,568
    edited 2009-04-13 05:19
    Sorry folks... I thought that I had found a trick.· I think the only way to·do this is to·do it completely within Assembly.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Beau Schwabe

    IC Layout Engineer
    Parallax, Inc.
  • stevenmess2004stevenmess2004 Posts: 1,102
    edited 2009-04-13 10:07
    hippy said...
    Dennis Ferron said...
    An old x86 trick involves pushing a desired address onto the stack, and then executing a ret instruction without a corresponding call first ... Maybe this could be accomplished with a similar stack hack?

    Unfortunately that simple hack will not work in Spin as calls are not performed to the address of the target method but are numeric indexes into a jump table, and the jump table also contains the equivalent of 'stack frame' adjustments which need to be executed before the call.

    I've never really looked at the stack framing but doesn't it basically store pcurr, dcurr and vcurr (plus something that I'm forgetting, stack pointer maybe?)? If it does than shouldn't you be able to change these three or four values and then when the method returns it will run some arbitrary code? It wouldn't really be a function call because you may not be able to guarantee that any variables on the stack kept their proper values. Getting back out of the arbitrary code could also be a problem...

    Something along these lines (not tested and won't work, just to give the general idea)
    CON
    pcurr=4 'or however many bytes it is back from the result position on the stack
    
    PUB main(jumpPosition)
      jumpPosition:=???'some method of finding where you want to jump to
      jumpSomewhere1(jumpPosition)
      doSomethingElse
    
    PUB jumpSomewhere1(jumpPosition) 'need this because there are two returns
      jumpSomewhere2(jumpPosition)
    
    PUB jumpSomewhere2(jumpPosition)|temp
      temp:=@result 'get position in stack
      long[noparse][[/noparse]temp-pcurr]:=jumpPositoin 'only changing pcurr, may or may not need to change the other two or three pointers depending on what you want to do
    
    PUB arbitraryMethod 'need to get the address of this somehow..., maybe dynamically load it from an SD card or eeprom
    
  • hippyhippy Posts: 1,981
    edited 2009-04-13 12:38
    It's been a while but the above mechanism from stevenmess2004 sounds right. The call to a method
    is the same as for any other language, push 'state information', call method, restore 'state' and return
    with result on stack if there is any result. Albeit that getting to the method involves a call via the jump
    table.

    This is a single 'via jump table' for a call into a method within the same object, a double-via for a call
    to a method in a different object. I cannot recall exactly what adjustments are made in on the way in
    to a method using the data in the jump table but recall it adjusts the base pointer for the start of an
    object's global variables. Make a call without adjusting things correctly and subsequent access to
    global vars is to memory in the wrong places.

    There's also another way to alter the PC by hacking bytecode to access the PC ( or SP ) within the
    interpreter Cog by use of "SPR" functions ...

    http://forums.parallax.com/showthread.php?p=739430

    Of course, there's no reason not to run a clone of the Spin Interpreter rather than the ROM version,
    intercept calls and handle them through some user supplied mechanism.

    Bottom line is; function pointers can be achieved, there are a number of mechanisms, but none of them
    are simple to implement, there's no definitive implementation, and the PropTool doesn't emit any
    information which makes the task easy or generic. Some work with re-entrant code, others do not.
  • stevenmess2004stevenmess2004 Posts: 1,102
    edited 2009-04-14 08:12
    The problem with a lot of this is that it breaks a lot of the simpleness of spin. Several things like this have come up that would often be really nice but I don't think that they fit into the way Chip wanted spin to work. As is, Spin does an excellent job of being a very simple and easy to use language for beginners which is who I think it was targeted at.

    I've looked a fair bit at dynamically loading spin objects from an sd card and have had it working reasonably well and also have ideas for how inheritance could be supported. However, since the compiler doesn't support a couple of things that need doing it's either a case of write a pre-processor or compiler to make it useful because there ends up being too many numbers that have to be put in by hand.

    Like hippy said, it's possible and actually not that hard to demonstrate the necessary techniques but it is a lot harder to make something which is 'coder friendly' and doesn't have unintended side affects.
Sign In or Register to comment.