master: Sketch out a way to trace/intercept foreign calls
snuglas via Sbcl-commits <[email protected]> Fri, 05 Jun 2026 17:03:19 +0000
| Newsgroups | gmane.lisp.steel-bank.cvs |
|---|---|
| Message-ID | <[email protected]> |
The branch "master" has been updated in SBCL:
via f3d711cd2af700a1ed9b22570987e7ccf94885bc (commit)
from 9deb748799125a1ee6c4feee6bfa22ddd2b035cc (commit)
- Log -----------------------------------------------------------------
commit f3d711cd2af700a1ed9b22570987e7ccf94885bc
Author: Douglas Katzman <[email protected]>
Date: Fri Jun 5 12:50:13 2026 -0400
Sketch out a way to trace/intercept foreign calls
---
src/code/alien-callback.lisp | 27 +++++++++++++++++++++++++++
tests/callback.impure.lisp | 21 +++++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/src/code/alien-callback.lisp b/src/code/alien-callback.lisp
index 1c04c7110..b55b1d695 100644
--- a/src/code/alien-callback.lisp
+++ b/src/code/alien-callback.lisp
@@ -354,6 +354,33 @@ function value."
(make-alien-pointer-type))
(cast (alien-callable-function lisp-name) (* t)))))
+;;; Changing the entry point of an alien linkage table entry allows testing without
+;;; the foreign library, or mocking of foreign routines. This is more powerful than
+;;; encapsulatig a function defined via DEFINE-ALIEN-ROUTINE because it catches uses
+;;; from bare (ALIEN-FUNCALL (EXTERN-ALIEN "f" ...)) and WITH-ALIEN.
+;;; Note also that a nonexistent foreign function can be "overridden".
+#+(or arm64 x86-64)
+(progn
+;; Not officially part of SB-ALIEN: interface, but need to protect from tree-shaker.
+(export 'sb-alien-internals::override-alien-linkage-entrypoint 'sb-alien-internals)
+(defun sb-alien-internals::override-alien-linkage-entrypoint (c-name new-value)
+ (let* ((linkage-index (sb-impl::ensure-alien-linkage-index c-name nil))
+ (new-jump-address
+ (etypecase new-value
+ (string (int-sap (find-foreign-symbol-address new-value)))
+ (symbol (alien-sap (gethash new-value sb-alien::*alien-callables*)))
+ (integer (int-sap new-value))))
+ (address-of-jump-address
+ #+arm64 (sap+ (int-sap (sb-vm::alien-linkage-index-to-addr linkage-index nil)) 8)
+ #+x86-64
+ ;; Access the linkage index as if data, even though it's a function- this computes
+ ;; the address of the word in the linkage space which needs to get overwritten.
+ ;; Computing as a function would get the immutable address within the space.
+ (int-sap (sb-vm::alien-linkage-index-to-addr linkage-index t))) ; datap = T
+ (original-jump-address (sap-ref-word address-of-jump-address 0)))
+ (setf (sap-ref-sap address-of-jump-address 0) new-jump-address)
+ (cons address-of-jump-address original-jump-address))))
+
(in-package "SB-THREAD")
#+sb-thread
(defun enter-foreign-callback (index return arguments)
diff --git a/tests/callback.impure.lisp b/tests/callback.impure.lisp
index 5df538a39..3289e1947 100644
--- a/tests/callback.impure.lisp
+++ b/tests/callback.impure.lisp
@@ -491,3 +491,24 @@
(setq sap (alien-sap callable)))
(assert (sb-sys:sap= sap (alien-sap callable)))
(assert (= (alien-funcall callable 1 2) (+ 3 i)))))))
+
+#+(or (and linux arm64) x86-64)
+(progn
+(defvar *got-arg*)
+(define-alien-callable intercepted-tan double ((x double))
+ (setf *got-arg* x)
+ ;; we'd have to do something to get the actual underlying function.
+ ;; For purposes of this test, just return anything.
+ 42.0d0)
+(with-test (:name :trace-foreign-call)
+ (let (restorer result)
+ (unwind-protect
+ (progn
+ (setq restorer
+ (sb-alien-internals:override-alien-linkage-entrypoint
+ "tan" 'intercepted-tan))
+ (setq result (tan (opaque-identity 0d0))))
+ (setf (sb-sys:sap-ref-word (car restorer) 0) (cdr restorer)))
+ (assert (eql *got-arg* 0d0))
+ (assert (= result 42.0d0))
+ (assert (eql (tan 0d0) 0d0))))) ; back to normal
-----------------------------------------------------------------------
hooks/post-receive
--
SBCL