December 03, 2020, 07:58:49 PM

Author Topic: [bmx] Replace Method at Runtime by N [ 1+ years ago ]  (Read 485 times)

Offline BlitzBot

  • Jr. Member
  • **
  • Posts: 1
[bmx] Replace Method at Runtime by N [ 1+ years ago ]
« on: June 29, 2017, 12:28:41 AM »
Title : Replace Method at Runtime
Author : N
Posted : 1+ years ago

Description : Ever wanted to replace a method at runtime?  No?  Didn't think so.  At any rate, I got bored and decided to see if I could do this, and it turns out I can.

For a simple explanation, this is how it works: every Blitz object has an is-a pointer, or a pointer to the class it is.  This is standard fare for a lot of languages.  Inside the class are the methods, the superclass, and debugging info.  Inside the debugging info is information about the class, essentially a list of methods, fields, metadata, and other bits of information pertaining to the class.  This is the important bit.  Using the debugging information, you can find out the location of the method (and, consequently, get a pointer to the method if you want [hint: you have to pass the Self object too if you're going to call it]) and you'll replace the old method's address with the new one.

Now, this probably isn't safe, and it probably isn't something you should be doing, but it's still neat and I figured I'd share it.

I've also got bits of code to create and load classes at runtime (that is, I'm creating new classes, not creating instances of classes), but that's still buggy and experimental.


Code :
Code: BlitzMax
  1. Strict
  2.  
  3. Global SCOPE_NAME:String[] = ["NULL", "FUNCTION", "USERTYPE", "LOCALBLOCK"]
  4. Const SCOPE_FUNCTION=1
  5. Const SCOPE_USERTYPE=2
  6. Const SCOPE_LOCALBLOCK=3
  7.  
  8. Global DECL_NAME:String[] = ["END", "CONST", "LOCAL", "FIELD", "GLOBAL", "VARPARAM", "TYPEMETHOD", "TYPEFUNCTION", "NULL"]
  9. Const DECL_END = 0
  10. Const DECL_CONST = 1
  11. Const DECL_LOCAL = 2
  12. Const DECL_FIELD = 3
  13. Const DECL_GLOBAL = 4
  14. Const DECL_VARPARAM = 5
  15. Const DECL_TYPEMETHOD = 6
  16. Const DECL_TYPEFUNCTION = 7
  17.  
  18. '#region Testing
  19.  
  20. Type DebugScope
  21.         Field _class:Int
  22.        
  23.         Field kind%
  24.         Field name$
  25.         Field decls:TList
  26.        
  27.         Method New()
  28.                 kind = 0
  29.                 name = ""
  30.                 decls = New TList
  31.         End Method
  32.        
  33.         Method Spit()
  34.                 Print "Kind: "+SCOPE_NAME[kind]
  35.                 Print "Name: "+name
  36.                 Print "Decls {"
  37.                 For Local i:DebugDecl = EachIn decls
  38.                         i.Spit(); Print ""
  39.                 Next
  40.                 Print "}"
  41.         End Method
  42.        
  43.         Function ForClass:DebugScope(cp@ Ptr)
  44.                 If cp = Null Then
  45.                         Return Null
  46.                 EndIf
  47.                
  48.                 Local scope:DebugScope = New DebugScope
  49.                 scope._class = Int cp
  50.                 Local p% Ptr = Int Ptr cp
  51.                 p = Int Ptr p[2]
  52.                 scope.kind = p[0]
  53.                 scope.name = String.FromCString(Byte Ptr p[1])
  54.                
  55.                 p = p + 2
  56.                
  57.                 While p[0]
  58.                         Local decl:DebugDecl = New DebugDecl
  59.                         decl.ref = p
  60.                         decl.kind = p[0]
  61.                         decl.name = String.FromCString(Byte Ptr p[1])
  62.                         decl.tag = String.FromCString(Byte Ptr p[2])
  63.                         decl.opaque = p[3]
  64.                         scope.decls.AddLast(decl)
  65.                         p :+ 4
  66.                 Wend
  67.                
  68.                 Return scope
  69.         End Function
  70.        
  71.         Function ForName:DebugScope(_type$)
  72.                 Local typeid:TTypeId = TTypeId.ForName(_type)
  73.                 If typeid = Null Then
  74.                         Return Null
  75.                 EndIf
  76.                 Return DebugScope.ForClass(Byte Ptr typeid._class)
  77.         End Function
  78.        
  79.         Method DeclForName:DebugDecl(declname$, declkind%)
  80.                 For Local i:DebugDecl = EachIn decls
  81.                         If i.kind = declkind And i.name = declname
  82.                                 Return i
  83.                         EndIf
  84.                 Next
  85.                 Return Null
  86.         End Method
  87. End Type
  88.  
  89. Type DebugDecl
  90.         Field ref@ Ptr
  91.         Field kind%
  92.         Field name$
  93.         Field tag$
  94.         Field opaque%
  95.        
  96.         Method New()
  97.                 kind = 8
  98.                 opaque = 0
  99.                 name = ""
  100.                 tag = ""
  101.         End Method
  102.        
  103.         Method Spit()
  104.                 Print "Kind:     "+DECL_NAME[kind]
  105.                 Print "Name:     "+name
  106.                 Print "Tag:      "+tag
  107.                 Select kind
  108.                         Case DECL_FIELD
  109.                                 Print "Index:    "+opaque
  110.                         Default
  111.                                 Print "Opaque:   "+opaque
  112.                 End Select
  113.         End Method
  114. End Type
  115.  
  116. '#endregion
  117.  
  118. Type Foobar
  119.         Field _name$
  120.        
  121.         Method setName( n$ )
  122.                 _name = n
  123.         End Method
  124.        
  125.         Method ToString$()
  126.                 Return "Normal"
  127.         End Method
  128. End Type
  129.  
  130. Function ReplaceMethod@ Ptr( _method:String, inClass:String, with@ Ptr, searchSuperTypes:Int = False )
  131.         Local result@ Ptr = Null
  132.         Local scope:DebugScope = DebugScope.ForName(inClass)
  133.         Local class:Int Ptr = Int Ptr scope._class
  134.         While scope And class
  135.                 Local decl:DebugDecl = scope.DeclForName(_method, DECL_TYPEMETHOD)
  136.                
  137.                 If decl = Null And searchSuperTypes Then
  138.                         class = Int Ptr class[0]
  139.                         scope = DebugScope.ForClass(class)
  140.                         Continue
  141.                 ElseIf decl = Null
  142.                         scope = Null
  143.                         class = Null
  144.                         Exit
  145.                 EndIf
  146.                
  147.                 Local mp@ Ptr Ptr = Byte Ptr Ptr(Byte Ptr(class)+decl.opaque)
  148.                 result = mp[0]
  149.                 mp[0] = with
  150.                
  151.                 scope = Null
  152.                 class = Null
  153.         Wend
  154.        
  155.         Return result
  156. End Function
  157.  
  158. Local foo:Foobar = New Foobar
  159.  
  160. Local scope:DebugScope = DebugScope.ForName("Foobar")
  161. scope.Spit()
  162.  
  163. Local decl:DebugDecl = scope.DeclForName("ToString", DECL_TYPEMETHOD)
  164.  
  165. Print foo.ToString()
  166. foo.setName("razzledazzlerootbeer")
  167.  
  168. Local oldMethod:String( obj:Object )
  169. oldMethod = ReplaceMethod( "ToString", "Foobar", newToString, False )
  170.  
  171. Print "New method: "+foo.ToString()
  172. Print "Old method: "+oldMethod( foo )
  173.  
  174. Function newToString:String( _self:Foobar )
  175.         Return "Foo._name = "+_self._name
  176. End Function


Comments :


GW(Posted 1+ years ago)

 Very Cool!


markcw(Posted 1+ years ago)

 Can't you do this kind of thing with reflection?


plash(Posted 1+ years ago)

 Awesome. Does the method have to be built with a parameter for itself or can you sneak that in in the pointer or something (for calling it like a pointer)?


N(Posted 1+ years ago)

 markcw: You can't use reflection to alter a class, which is what I'm doing.Plash: It has to have a parameter for itself, as I do not know how you could slip the object into the call without wrapping it (see TMethod).


N(Posted 1+ years ago)

 I added a ReplaceMethod function that should make it relatively easier to understand the exact process of replacing methods.It's also worth noting that certain methods cannot be replaced using the debugging info- specifically those in Object, String, and the array types.  Those can be replaced, but you will have to know the offsets of the specific methods ahead of time, rather than using the debugging info to find them.Oddly enough, the New/Delete methods can be replaced, but I wouldn't recommend attempting this at all.  These methods are the main reason why I'm having trouble inserting new classes into the application at runtime, since there's no real explanation of how to write the constructor in its entirety.


Azathoth(Posted 1+ years ago)

 Interesting. Does it have to be run in debug mode?Edit: Is it possible to change the method of a single instance rather than a class?


N(Posted 1+ years ago)

 I don't believe so, and no.


N(Posted 1+ years ago)

 This is sort of old, but I was revisiting the idea, and this..<div class="quote"> Edit: Is it possible to change the method of a single instance rather than a class?  </div>The answer is yes.You'll have to duplicate the object's class, modify the duplicate's methods, and change the instance's isa pointer.  Downside is you'll have to modify bbObjectFree such that it deletes the duplicated class once the object is disposed of.Unfortunately, I have no code to show for this right now, but it should be easy enough to do since you're just copying the existing class.


 

SimplePortal 2.3.6 © 2008-2014, SimplePortal