FFI: support symbol lookup without specifying lib on Windows

This works similar to `dlsym(RTLD_DEFAULT, …)` with the caveat that
symbols on Windows may not be unique, and are usually qualified by the
module they are exported from.  That means that wrong symbols may be
fetched, potentially causing serious issues; therefore this usage is
not recommended for production purposes, but is a nice simplification
for quick experiments and the ext/ffi test suite.

Closes GH-16351.
This commit is contained in:
Christoph M. Becker 2024-10-11 17:50:15 +02:00
parent 9504fcfc0f
commit db991bc0f1
No known key found for this signature in database
GPG key ID: D66C9593118BCCB6
17 changed files with 57 additions and 135 deletions

View file

@ -116,6 +116,11 @@ PHP 8.5 UPGRADE NOTES
PHP_RELEASE_VERSION are now always numbers. Previously, they have been
strings for buildconf builds.
* FFI:
. It is no longer necessary to specify the library when using FFI::cdef()
and FFI::load(). However, this convenience feature should not be used in
production.
========================================
13. Other Changes
========================================

View file

@ -2974,6 +2974,40 @@ static zend_always_inline bool zend_ffi_validate_api_restriction(zend_execute_da
} \
} while (0)
#ifdef PHP_WIN32
# ifndef DWORD_MAX
# define DWORD_MAX ULONG_MAX
# endif
# define NUM_MODULES 1024
/* A rough approximation of dlysm(RTLD_DEFAULT) */
static void *dlsym_loaded(char *symbol)
{
HMODULE modules_static[NUM_MODULES], *modules = modules_static;
DWORD num = NUM_MODULES, i;
void * addr;
if (!EnumProcessModules(GetCurrentProcess(), modules, num * sizeof(HMODULE), &num)) {
return NULL;
}
if (num >= NUM_MODULES && num <= DWORD_MAX / sizeof(HMODULE)) {
modules = emalloc(num *sizeof(HMODULE));
if (!EnumProcessModules(GetCurrentProcess(), modules, num * sizeof(HMODULE), &num)) {
efree(modules);
return NULL;
}
}
for (i = 0; i < num; i++) {
addr = GetProcAddress(modules[i], symbol);
if (addr != NULL) break;
}
if (modules != modules_static) {
efree(modules);
}
return addr;
}
# undef DL_FETCH_SYMBOL
# define DL_FETCH_SYMBOL(h, s) (h == NULL ? dlsym_loaded(s) : GetProcAddress(h, s))
#endif
ZEND_METHOD(FFI, cdef) /* {{{ */
{
zend_string *code = NULL;

View file

@ -3,10 +3,9 @@ FFI 100: PHP symbols
--EXTENSIONS--
ffi
--SKIPIF--
<?php require_once('utils.inc'); ?>
<?php
try {
ffi_cdef("extern void *zend_printf;", ffi_get_php_dll_name());
FFI::cdef("extern void *zend_printf;");
} catch (Throwable $e) {
die('skip PHP symbols not available');
}
@ -17,7 +16,7 @@ ffi.enable=1
<?php
require_once('utils.inc');
$fastcall = ffi_get_fastcall_specifier();
$zend = ffi_cdef("
$zend = FFI::cdef("
const char *get_zend_version(void);
//char *get_zend_version(void);
extern size_t (*zend_printf)(const char *format, ...);
@ -26,7 +25,7 @@ $zend = ffi_cdef("
void $fastcall zend_str_tolower(char *str, size_t length);
", ffi_get_php_dll_name());
");
var_dump(trim(explode("\n",$zend->get_zend_version())[0]));
//var_dump(trim(FFI::string($zend->get_zend_version())));
var_dump($zend->zend_printf);

View file

@ -3,10 +3,9 @@ FFI 101: PHP symbols (function address)
--EXTENSIONS--
ffi
--SKIPIF--
<?php require_once('utils.inc'); ?>
<?php
try {
ffi_cdef("extern void *zend_printf;", ffi_get_php_dll_name());
FFI::cdef("extern void *zend_printf;");
} catch (Throwable $e) {
die('skip PHP symbols not available');
}
@ -17,7 +16,7 @@ ffi.enable=1
<?php
require_once('utils.inc');
$fastcall = ffi_get_fastcall_specifier();
$zend = ffi_cdef("
$zend = FFI::cdef("
const char *get_zend_version(void);
//char *get_zend_version(void);
extern size_t (*zend_printf)(const char *format, ...);
@ -26,7 +25,7 @@ $zend = ffi_cdef("
void $fastcall zend_str_tolower(char *str, size_t length);
", ffi_get_php_dll_name());
");
$f = $zend->get_zend_version;
var_dump(trim(explode("\n",$f())[0]));
//var_dump(trim(FFI::string($zend->get_zend_version())));

View file

@ -6,7 +6,7 @@ ffi
<?php require_once('utils.inc'); ?>
<?php
try {
FFI::cdef("void* zend_write;", ffi_get_php_dll_name());
FFI::cdef("void* zend_write;");
} catch (Throwable $e) {
die('skip PHP symbols not available');
}
@ -20,7 +20,7 @@ require_once('utils.inc');
$zend = FFI::cdef("
typedef size_t (*zend_write_func_t)(const char *str, size_t str_length);
extern zend_write_func_t zend_write;
", ffi_get_php_dll_name());
");
echo "Hello World!\n";

View file

@ -1,4 +0,0 @@
#define FFI_SCOPE "TEST_300_WIN32"
#define FFI_LIB "PHP_DLL_NAME"
size_t php_printf(const char *format, ...);

View file

@ -1,29 +0,0 @@
--TEST--
FFI 301: FFI loading on Windows
--EXTENSIONS--
ffi
--SKIPIF--
<?php if (substr(PHP_OS, 0, 3) != 'WIN') die('skip for Windows only'); ?>
--INI--
ffi.enable=1
--FILE--
<?php
require_once('utils.inc');
$fn = __DIR__ . "/300-win32.h";
$cont = str_replace(
"PHP_DLL_NAME",
ffi_get_php_dll_name(),
file_get_contents("$fn.in")
);
file_put_contents($fn, $cont);
$ffi = FFI::load($fn);
$ffi->php_printf("Hello World from %s!\n", "PHP");
?>
--CLEAN--
<?php
$fn = __DIR__ . "/300-win32.h";
unlink($fn);
?>
--EXPECT--
Hello World from PHP!

View file

@ -2,8 +2,6 @@
FFI 301: FFI loading
--EXTENSIONS--
ffi
--SKIPIF--
<?php if (substr(PHP_OS, 0, 3) == 'WIN') die('skip not for Windows'); ?>
--INI--
ffi.enable=1
--FILE--

View file

@ -4,9 +4,8 @@ Bug #77632 (FFI function pointers with variadics)
ffi
--SKIPIF--
<?php
require_once('utils.inc');
try {
FFI::cdef("extern void *zend_printf;", ffi_get_php_dll_name());
FFI::cdef("extern void *zend_printf;");
} catch (Throwable $_) {
die('skip PHP symbols not available');
}
@ -15,8 +14,7 @@ try {
ffi.enable=1
--FILE--
<?php
require_once('utils.inc');
$libc = FFI::cdef("extern size_t (*zend_printf)(const char *format, ...);", ffi_get_php_dll_name());
$libc = FFI::cdef("extern size_t (*zend_printf)(const char *format, ...);");
$args = ["test from zend_printf\n"];
($libc->zend_printf)(...$args);
$args2 = ["Hello, %s from zend_printf\n", "world"];

View file

@ -6,9 +6,8 @@ ffi
ffi.enable=1
--FILE--
<?php
require_once('utils.inc');
$def = 'char * __cdecl get_zend_version(void);';
$ffi = ffi_cdef($def, ffi_get_php_dll_name());
$ffi = FFI::cdef($def);
echo substr(FFI::string($ffi->get_zend_version()), 0, 4) . "\n";
?>
--EXPECT--

View file

@ -5,7 +5,6 @@ ffi
zend_test
--FILE--
<?php
require_once('utils.inc');
$header = <<<HEADER
struct bug79096 {
uint64_t a;
@ -15,16 +14,7 @@ struct bug79096 {
struct bug79096 bug79096(void);
HEADER;
if (PHP_OS_FAMILY !== 'Windows') {
$ffi = FFI::cdef($header);
} else {
try {
$ffi = FFI::cdef($header, 'php_zend_test.dll');
} catch (FFI\Exception $ex) {
$ffi = FFI::cdef($header, ffi_get_php_dll_name());
}
}
$ffi = FFI::cdef($header);
$struct = $ffi->bug79096();
var_dump($struct);
?>

View file

@ -5,22 +5,12 @@ ffi
zend_test
--FILE--
<?php
require_once __DIR__ . '/utils.inc';
$header = <<<HEADER
extern int *(*bug79177_cb)(void);
void bug79177(void);
HEADER;
if (PHP_OS_FAMILY !== 'Windows') {
$ffi = FFI::cdef($header);
} else {
try {
$ffi = FFI::cdef($header, 'php_zend_test.dll');
} catch (FFI\Exception $ex) {
$ffi = FFI::cdef($header, ffi_get_php_dll_name());
}
}
$ffi = FFI::cdef($header);
$ffi->bug79177_cb = function() {
throw new \RuntimeException('Not allowed');
};

View file

@ -5,22 +5,11 @@ ffi
zend_test
--FILE--
<?php
require_once('utils.inc');
$header = <<<HEADER
void bug79532(off_t *array, size_t elems);
HEADER;
if (PHP_OS_FAMILY !== 'Windows') {
$ffi = FFI::cdef($header);
} else {
try {
$ffi = FFI::cdef($header, 'php_zend_test.dll');
} catch (FFI\Exception $ex) {
$ffi = FFI::cdef($header, ffi_get_php_dll_name());
}
}
$ffi = FFI::cdef($header);
$array = FFI::cdef()->new("off_t[3]");
$ffi->bug79532($array, 3);
var_dump($array);

View file

@ -9,7 +9,6 @@ if (PHP_OS_FAMILY == 'Windows' && ((1 << 31) > 0)) die('xfail libffi doesn\'t pr
?>
--FILE--
<?php
require_once('utils.inc');
$header = <<<HEADER
typedef struct bug80847_01 {
uint64_t b;
@ -23,15 +22,7 @@ $header = <<<HEADER
bug80847_02 ffi_bug80847(bug80847_02 s);
HEADER;
if (PHP_OS_FAMILY !== 'Windows') {
$ffi = FFI::cdef($header);
} else {
try {
$ffi = FFI::cdef($header, 'php_zend_test.dll');
} catch (FFI\Exception $ex) {
$ffi = FFI::cdef($header, ffi_get_php_dll_name());
}
}
$ffi = FFI::cdef($header);
$x = $ffi->new('bug80847_02');
$x->a->b = 42;
$x->a->c = 42.5;

View file

@ -6,7 +6,6 @@ zend_test
--FILE--
<?php
require_once 'utils.inc';
$h = <<<'EOD'
void (*bug_gh9090_void_none_ptr)();
void (*bug_gh9090_void_int_char_ptr)(int, char *);
@ -19,15 +18,7 @@ void bug_gh9090_void_int_char(int i, char *s);
void bug_gh9090_void_int_char_var(int i, char *fmt, ...);
EOD;
if (PHP_OS_FAMILY !== 'Windows') {
$ffi = FFI::cdef($h);
} else {
try {
$ffi = FFI::cdef($h, 'php_zend_test.dll');
} catch (FFI\Exception $ex) {
$ffi = FFI::cdef($h, ffi_get_php_dll_name());
}
}
$ffi = FFI::cdef($h);
$func_ptrs = [
'bug_gh9090_void_none_ptr',

View file

@ -5,21 +5,11 @@ ffi
zend_test
--FILE--
<?php
require_once __DIR__ . '/utils.inc';
$header = <<<HEADER
extern int gh11934b_ffi_var_test_cdata;
HEADER;
if (PHP_OS_FAMILY !== 'Windows') {
$ffi = FFI::cdef($header);
} else {
try {
$ffi = FFI::cdef($header, 'php_zend_test.dll');
} catch (FFI\Exception $ex) {
$ffi = FFI::cdef($header, ffi_get_php_dll_name());
}
}
$ffi = FFI::cdef($header);
$ffi->gh11934b_ffi_var_test_cdata->cdata = 2;
var_dump($ffi->gh11934b_ffi_var_test_cdata);
$source = $ffi->new('int');

View file

@ -1,28 +1,10 @@
<?php
function ffi_cdef($code, $lib)
{
if (isset($lib)) {
return FFI::cdef($code, $lib);
} else {
return FFI::cdef($code);
}
}
function ffi_get_php_dll_name()
{
if (PHP_OS_FAMILY === 'Windows') {
return "php" . PHP_MAJOR_VERSION . (PHP_ZTS ? "ts" : "") . (PHP_DEBUG ? "_debug" : "") . ".dll";
} else {
return null;
}
}
function ffi_get_fastcall_specifier()
{
foreach (['__attribute__((fastcall))', '__fastcall', '__vectorcall'] as $spec) {
try {
ffi_cdef("extern size_t $spec zend_list_insert(void *ptr, int type);", ffi_get_php_dll_name());
FFI::cdef("extern size_t $spec zend_list_insert(void *ptr, int type);");
return "$spec ";
} catch (Throwable $e) {}
}