-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fix GH-16013 and bug #80857: Big endian issues #17255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ext/ffi/ffi.c
Outdated
@@ -746,6 +762,36 @@ static zend_always_inline zend_result zend_ffi_zval_to_cdata(void *ptr, zend_ffi | |||
zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(value); | |||
if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type)) && | |||
type->size == ZEND_FFI_TYPE(cdata->type)->size) { | |||
if (is_ret && type->size < sizeof(ffi_arg)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an ugly solution but I don't see a better way so far. (memcpy trick may be possible for unsigned types but gets a bit uglier for sign extending signed types)
Also, perhaps it is better to put this inside an ifdef for big endian to reduce code bloat but I'm not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be fixed in a much simpler way. See comments.
ext/ffi/ffi.c
Outdated
zend_ffi_zval_to_cdata(ret, ret_type, &retval); | ||
zend_ffi_zval_to_cdata(ret, ret_type, &retval, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of zend_ffi_zval_to_cdata()
modification, we should widen ret_type
to intptr_t
(or whatever else that ABI requests). We should keep the signess of the origianal ret_type
and the capacity of ffi_arg
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got rid of the ZEND_FFI_WRITE_NARROW
macro in a separate commit to this PR.
I needed to keep the is_ret
addition to handle cdata objects specially because we must not overread the pointer. I do see the appeal of this approach although I found the ZEND_FFI_WRITE_NARROW
approach also clear.
ext/ffi/ffi.c
Outdated
@@ -560,22 +576,22 @@ static zend_always_inline void zend_ffi_cdata_to_zval(zend_ffi_cdata *cdata, voi | |||
return; | |||
#endif | |||
case ZEND_FFI_TYPE_UINT8: | |||
ZVAL_LONG(rv, *(uint8_t*)ptr); | |||
ZVAL_LONG(rv, ZEND_FFI_READ_NARROW(uint8_t, ptr, is_ret)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of zend_ffi_cdata_to_zval()
modification, we should pass the "proper" type at the first place (in ffi_trampoline
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I committed this separately to this PR.
I was able to get rid of the ZEND_FFI_READ_NARROW
macros, but I needed to keep the special handling of ZEND_FFI_TYPE_BOOL
to use ZVAL_BOOL
and ZEND_FFI_TYPE_CHAR
with ZVAL_CHAR
. So therefore I kept the return handling in this function. I'm not sure this is much simpler, I preferred the old way to be honest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I applied your suggestion in 2 new commits. I'm not entirely convinced it's much simpler, but maybe I did something wrong
ext/ffi/ffi.c
Outdated
@@ -560,22 +576,22 @@ static zend_always_inline void zend_ffi_cdata_to_zval(zend_ffi_cdata *cdata, voi | |||
return; | |||
#endif | |||
case ZEND_FFI_TYPE_UINT8: | |||
ZVAL_LONG(rv, *(uint8_t*)ptr); | |||
ZVAL_LONG(rv, ZEND_FFI_READ_NARROW(uint8_t, ptr, is_ret)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I committed this separately to this PR.
I was able to get rid of the ZEND_FFI_READ_NARROW
macros, but I needed to keep the special handling of ZEND_FFI_TYPE_BOOL
to use ZVAL_BOOL
and ZEND_FFI_TYPE_CHAR
with ZVAL_CHAR
. So therefore I kept the return handling in this function. I'm not sure this is much simpler, I preferred the old way to be honest.
ext/ffi/ffi.c
Outdated
zend_ffi_zval_to_cdata(ret, ret_type, &retval); | ||
zend_ffi_zval_to_cdata(ret, ret_type, &retval, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got rid of the ZEND_FFI_WRITE_NARROW
macro in a separate commit to this PR.
I needed to keep the is_ret
addition to handle cdata objects specially because we must not overread the pointer. I do see the appeal of this approach although I found the ZEND_FFI_WRITE_NARROW
approach also clear.
I came to the following patch. May be we may wrap this code with diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c
index d1713b20d75..a660274e75e 100644
--- a/ext/ffi/ffi.c
+++ b/ext/ffi/ffi.c
@@ -985,6 +985,24 @@ static void zend_ffi_callback_trampoline(ffi_cif* cif, void* ret, void** args, v
ret_type = ZEND_FFI_TYPE(callback_data->type->func.ret_type);
if (ret_type->kind != ZEND_FFI_TYPE_VOID) {
zend_ffi_zval_to_cdata(ret, ret_type, &retval);
+ if (ret_type->size < sizeof(ffi_arg)
+ && ret_type->kind >= ZEND_FFI_TYPE_UINT8
+ && ret_type->kind < ZEND_FFI_TYPE_POINTER) {
+ /* We need to widen the value (zero extend) */
+ switch (ret_type->size) {
+ case 1:
+ *(ffi_arg*)ret = *(uint8_t*)ret;
+ break;
+ case 2:
+ *(ffi_arg*)ret = *(uint16_t*)ret;
+ break;
+ case 4:
+ *(ffi_arg*)ret = *(uint32_t*)ret;
+ break;
+ default:
+ break;
+ }
+ }
}
zval_ptr_dtor(&retval);
@@ -2855,7 +2873,28 @@ static ZEND_FUNCTION(ffi_trampoline) /* {{{ */
}
if (ZEND_FFI_TYPE(type->func.ret_type)->kind != ZEND_FFI_TYPE_VOID) {
- zend_ffi_cdata_to_zval(NULL, ret, ZEND_FFI_TYPE(type->func.ret_type), BP_VAR_R, return_value, 0, 1, 0);
+ zend_ffi_type *ret_type = ZEND_FFI_TYPE(type->func.ret_type);
+
+ if (ret_type->size < sizeof(ffi_arg)
+ && ret_type->kind >= ZEND_FFI_TYPE_UINT8
+ && ret_type->kind < ZEND_FFI_TYPE_POINTER) {
+
+ /* We need to narrow the value (truncate) */
+ switch (ret_type->size) {
+ case 1:
+ *(uint8_t*)ret = *(ffi_arg*)ret;
+ break;
+ case 2:
+ *(uint16_t*)ret = *(ffi_arg*)ret;
+ break;
+ case 4:
+ *(uint32_t*)ret = *(ffi_arg*)ret;
+ break;
+ default:
+ break;
+ }
+ }
+ zend_ffi_cdata_to_zval(NULL, ret, ret_type, BP_VAR_R, return_value, 0, 1, 0);
} else {
ZVAL_NULL(return_value);
} |
Yes that works. Adding the ifdef is a good idea too. I'll force push this PR with the ifdef and commit after CI passes. |
The FFI call return values follow widening rules. We must widen to `ffi_arg` in the case we're handling a return value for types shorter than the machine width. From http://www.chiark.greenend.org.uk/doc/libffi-dev/html/The-Closure-API.html: > In most cases, ret points to an object of exactly the size of the type specified when cif was constructed. > However, integral types narrower than the system register size are widened. > In these cases your program may assume that ret points to an ffi_arg object. If we don't do this, we get wrong values when reading the return values. Co-authored-by: Dmitry Stogov <dmitry@zend.com>
762dd09
to
020b2e9
Compare
The FFI call return values follow widening rules. We must widen to `ffi_arg` in the case we're handling a return value for types shorter than the machine width. From http://www.chiark.greenend.org.uk/doc/libffi-dev/html/The-Closure-API.html: > In most cases, ret points to an object of exactly the size of the type specified when cif was constructed. > However, integral types narrower than the system register size are widened. > In these cases your program may assume that ret points to an ffi_arg object. If we don't do this, we get wrong values when reading the return values. Closes phpGH-17255. Co-authored-by: Dmitry Stogov <dmitry@zend.com>
Fixes GH-16013 and https://bugs.php.net/bug.php?id=80857
The FFI call return values follow widening rules.
We must widen to
ffi_arg
in the case we're handling a return value for types shorter than the machine width. From http://www.chiark.greenend.org.uk/doc/libffi-dev/html/The-Closure-API.html:If we don't do this, we get wrong values when reading the return values on big endian.
This patch was developed and tested on a PowerPC 64-bit big endian system.
This worked by coincidence on little endian because the bytes are in the right place.