-
Notifications
You must be signed in to change notification settings - Fork 147
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
Always return unaltered VM dispatch (PHP 7) #762
Conversation
9ab5f7f
to
ff0e81b
Compare
ff0e81b
to
75b7660
Compare
69fb1b1
to
64ea72a
Compare
src/ext/php7/engine_hooks.c
Outdated
if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { | ||
ZVAL_DEREF(q); | ||
if (Z_OPT_REFCOUNTED_P(q)) { | ||
Z_ADDREF_P(q); | ||
} | ||
} else { | ||
q = &EG(uninitialized_zval); | ||
} |
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.
Presumably EG(uninitialized_zval)
is IS_UNDEF
; is that right? If so, I think this can be reduced.
if (EXPECTED(Z_TYPE_INFO_P(q) != IS_UNDEF)) { | |
ZVAL_DEREF(q); | |
if (Z_OPT_REFCOUNTED_P(q)) { | |
Z_ADDREF_P(q); | |
} | |
} else { | |
q = &EG(uninitialized_zval); | |
} | |
ZVAL_DEREF(q); | |
Z_TRY_ADDREF_P(q); |
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.
Strangely enough, EG(uninitialized_zval)
is initialized to null. I think that's for BC.
95cb9d1
to
b22d6a1
Compare
301112c
to
2dc9f84
Compare
4054424
to
8b03b61
Compare
f4b254f
to
e9cae80
Compare
fd771bc
to
6c795fe
Compare
It's going to take a while to comb over. In the meantime, can you think of a way to instrument |
Unfortunately not. Internal function's don't emit a |
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.
Looking pretty good! I've recommended few small style changes so that delegating to previous handler doesn't get repeated quite so much.
db8325e
to
c6ff13d
Compare
This ensures that the return handler can close the span and provide the return value
Co-authored-by: Levi Morrison <levi.morrison@datadoghq.com>
c6ff13d
to
8b4b8c2
Compare
Description
This PR addresses part two of a two-part refactor of the opcode handlers. The the first part was merged in #754. This PR changes the way the tracer hooks into the engine for instrumented calls for PHP 7. PHP 5 will be addressed in a separate PR.
Before this PR, instrumented calls would be forwarded from within the custom handlers for
ZEND_DO_FCALL
and friends. This required the handlers to returnZEND_USER_OPCODE_LEAVE
to the VM which would prevent any downstream neighboring extensions from reliably hooking the function call opcodes.This PR will start the span and run any prehook tracking closures in the function-call handlers and immediately return
ZEND_USER_OPCODE_DISPATCH
to the VM to resume normal execution. The span is stopped by hooking return opcodes. Exceptions are tracked via theZEND_HANDLE_EXCEPTION
pseudo opcode. Since all the opcode handlers allow the VM to continue its expected path unaltered, downstream neighboring extensions can reliably hook the function call opcodes for every call.Nice-Neighbor Framework Results
This change was tested on the nice-neighbor framework to ensure that the new design allowed neighboring extensions to function without adverse effects from the tracer.
dd_fcall_dump
The fake
dd_fcall_dump
extension prints all function-call related opcodes. This is a nice neighbor that runs any upstream neighboring handlers before running its own. If it detects a change in the VM state (if the upstream handlers return anything other thanZEND_USER_OPCODE_DISPATCH
) it will not run its own handler.Before this change, when
dd_fcall_dump
is loaded afterddtrace
, the following output is produced when running drop_spans.phpt:The "VM state change detected" messages are from
dd_fcall_dump
. This reveals two fundamental issues.ddtrace
is altering the VM state (returningZEND_USER_OPCODE_LEAVE
from the opcode handler), anddd_fcall_dump
is unable to reliably hook those opcodes.ZEND_USER_OPCODE_LEAVE
must manually increment the oplineEX(opline)++
. However, neighboring extension's opcode handlers are still run for the original opcode with theexecute_data
mutated. For example, in the message above "VM state change detected. Cannot handle OPCODE (ZEND_INIT_FCALL)", that message was generated by theZEND_DO_ICALL
opcode handler. Thedd_fcall_dump
extension does not hookZEND_INIT_FCALL
, but sinceddtrace
mutated the VM state, theZEND_INIT_FCALL
was being passed to itsZEND_DO_ICALL
opcode handler.After this change, the output reflects
ddtrace
being a nice neighbor by not altering the VM state and allowingdd_fcall_dump
to successfully hook the intended opcodes with the proper handlers.You might also have noticed that all the
ZEND_DO_ICALL
opcodes are now being emitted asZEND_DO_FCALL
. This is a direct result of the new design overridingzend_execute_internal
which changes the opcode that the compiler emits.This design does not hook
zend_execute_ex
in order to avoid the stack limitations that that override introduces. This means userland functions will continue to emitZEND_DO_UCALL
as illustrated by running exit_and_drop_span.phpt.Side Effects
Argument Mutation
The primary side effect of this new design relates to the mutability of arguments within an instrumented call. With this change, the tracing closure has access to the same arguments passed to the original call. If the original call mutates an argument, the posthook tracing closure will receive the mutated argument. This is the expected behavior of arguments in PHP 7.
The best way to illustrate this is with an example:
On PHP 7, this outputs:
This illustrates that arguments in PHP 7 are mutable in a sense. If a posthook tracing closure were to access argument
$a
, it would be set to "Dogs". Before this change, the tracing closure would receive$a
in its state before mutation and would be set to "Cats". If an argument needs to be accessed before mutation, the tracing closure can be run as a prehook to access the arguments before the original call.So given:
Posthook example:
Prehook example:
A few integrations have been changed to prehooks on PHP 7 to circumvent argument mutation. The Symfony integration was changed preceding this PR: #786.
Generators
Another side effect of the new design impacts function calls that return a generator with
yield
andyield from
. A current limitation of the existing design is that generators are only traced until the firstyield
. Subsequentyield
's in an iteration are not instrumented. The following code illustrates this limitation:The same limitation exists in this new design, but now the return value is passed to the tracing closure differently. Before this change, the return value is provided to the tracing closure as an instance of
Generator
. With the new design, the return values are passed to the tracing closure directly from the opline (before theZEND_YIELD
opcode has run.) Therefore the return value provided to the tracing closure is now the actual value provided by theyield
statement, not an instance ofGenerator
. The following code demonstrates this behavior change:Existing behavior output:
New behavior output:
Readiness checklist
Reviewer checklist