Hi!
While investigating an issue with Xdebug and fast_call/fast_ret, I
noticed that the opcodes "associated" with the "finally" statement, are
rolled up in the previous line.
The code:
1 <?php
2 function extractFile()
3 {
4 try {
5 echo "try\n";
6 } catch (Catch1 $e) {
7 echo "catch1\n";
8 } catch (Catch2 $e) {
9 echo "catch2\n";
10 } finally {
11 echo "finally\n";
12 }
13 echo "end\n";
14 }
15
16 extractFile();
17 ?>
Produces:
line #* E I O op fetch ext return operands
5 0 E > ECHO 'try%0A'
1 > JMP ->7
6 2 E > > CATCH 'Catch1', !0, ->5
7 3 > ECHO 'catch1%0A'
4 > JMP ->7
8 5 E > > CATCH 'Catch2', !0
9 6 > ECHO 'catch2%0A'
7 > > FAST_CALL ->9
8* JMP ->11
11 9 > ECHO 'finally%0A'
10 > FAST_RET
13 11 > ECHO 'end%0A'
14 12 > RETURN null
The FAST_CALL/JMP instructions should really be linked to line 10. Not
doing so produces confusing results while doing code coverage, where it now
shows that the "echo catch2\n" on line 9 is executed. This is of course not
the case, but the linking of FAST_CALL and JMP to line 9 makes it look like
this. Is it possible to change this so thta the FAST_CALL and JMP are linked
to line 10 instead?
And secondly, I am struggeling with where FAST_CALL and FAST_RET can jump to.
Right now, I have:
+#if PHP_VERSION_ID
>= 50500
-
} else if (opcode.opcode == ZEND_FAST_CALL) {
+#if PHP_VERSION_ID
>= 70000
-
*jmp1 = VLD_ZNODE_JMP_LINE(opcode.op1, position, base_address);
+#else
-
*jmp1 = ((long) VLD_ZNODE_ELEM(opcode.op1, jmp_addr) - (long) base_address) / sizeof(zend_op);
+#endif
-
if (opcode.extended_value) {
-
*jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
-
}
-
return 1;
-
} else if (opcode.opcode == ZEND_FAST_RET) {
-
*jmp1 = position + 1;
-
if (opcode.extended_value) {
-
*jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
-
}
-
return 1;
+#endif
Which seems to work, although I am unsure about the "+ 1" for FAST_RET.
I also never see anything for opcode.extended_value, and hence the *jmp is
unset. This also returns dumping opcodes with this bit of code (code slightly
lifted from dbg):
+#if PHP_VERSION_ID
>= 50600
-
switch (op.opcode) {
-
case ZEND_FAST_RET:
-
if (op.extended_value == ZEND_FAST_RET_TO_FINALLY) {
-
fetch_type = "to_finally";
-
} else if (op.extended_value == ZEND_FAST_RET_TO_CATCH) {
-
fetch_type = "to_catch";
-
}
-
break;
-
case ZEND_FAST_CALL:
-
if (op.extended_value == ZEND_FAST_CALL_FROM_FINALLY)
{
-
fetch_type = "from_finally";
-
}
-
break;
-
}
+#endif
Any hints?
cheers,
Derick
http://derickrethans.nl | http://xdebug.org
Like Xdebug? Consider a donation: http://xdebug.org/donate.php
twitter: @derickr and @xdebug
Posted with an email client that doesn't mangle email: alpine
Hi!
While investigating an issue with Xdebug and fast_call/fast_ret, I
noticed that the opcodes "associated" with the "finally" statement, are
rolled up in the previous line.The code:
1 <?php
2 function extractFile()
3 {
4 try {
5 echo "try\n";
6 } catch (Catch1 $e) {
7 echo "catch1\n";
8 } catch (Catch2 $e) {
9 echo "catch2\n";
10 } finally {
11 echo "finally\n";
12 }
13 echo "end\n";
14 }
15
16 extractFile();
17 ?>Produces:
line #* E I O op fetch ext return
operands
5 0 E > ECHO
'try%0A'
1 > JMP
->7
6 2 E > > CATCH
'Catch1', !0, ->5
7 3 > ECHO
'catch1%0A'
4 > JMP
->7
8 5 E > > CATCH
'Catch2', !0
9 6 > ECHO
'catch2%0A'
7 > > FAST_CALL
->9
8* JMP
->11
11 9 > ECHO
'finally%0A'
10 > FAST_RET
13 11 > ECHO
'end%0A'
14 12 > RETURN
nullThe FAST_CALL/JMP instructions should really be linked to line 10. Not
doing so produces confusing results while doing code coverage, where it now
shows that the "echo catch2\n" on line 9 is executed. This is of course not
the case, but the linking of FAST_CALL and JMP to line 9 makes it look like
this. Is it possible to change this so thta the FAST_CALL and JMP are
linked
to line 10 instead?
Yeah, you're right. Should be fixed with
https://github.com/php/php-src/commit/b3afeeabefc4777ec4797a7e2c3688e9e20be4cc
.
And secondly, I am struggeling with where FAST_CALL and FAST_RET can jump
to.
Right now, I have:+#if
PHP_VERSION_ID
>= 50500
} else if (opcode.opcode == ZEND_FAST_CALL) {
+#if
PHP_VERSION_ID
>= 70000
*jmp1 = VLD_ZNODE_JMP_LINE(opcode.op1, position,
base_address);
+#else
*jmp1 = ((long) VLD_ZNODE_ELEM(opcode.op1, jmp_addr) -
(long) base_address) / sizeof(zend_op);
+#endif
if (opcode.extended_value) {
*jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
}
return 1;
} else if (opcode.opcode == ZEND_FAST_RET) {
*jmp1 = position + 1;
if (opcode.extended_value) {
*jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
}
return 1;
+#endif
Which seems to work, although I am unsure about the "+ 1" for FAST_RET.
For PHP 7 only: FAST_CALL always jumps to op1. op2 is not a jmp addr, it's
a try_catch_array offset. For FAST_RET there are no jump addresses encoded
in the opline. It will either jump back to one past the invoking FAST_CALL
(of which there may be multiple), or (if finally is executed due to an
uncaught exception) it will jump back to the next applicable catch or
finally or leave the function.
Nikita
I also never see anything for opcode.extended_value, and hence the *jmp is
unset. This also returns dumping opcodes with this bit of code (code
slightly
lifted from dbg):+#if
PHP_VERSION_ID
>= 50600
switch (op.opcode) {
case ZEND_FAST_RET:
if (op.extended_value == ZEND_FAST_RET_TO_FINALLY)
{
fetch_type = "to_finally";
} else if (op.extended_value ==
ZEND_FAST_RET_TO_CATCH) {
fetch_type = "to_catch";
}
break;
case ZEND_FAST_CALL:
if (op.extended_value ==
ZEND_FAST_CALL_FROM_FINALLY)
{
fetch_type = "from_finally";
}
break;
}
+#endif
Any hints?
cheers,
Derickhttp://derickrethans.nl | http://xdebug.org
Like Xdebug? Consider a donation: http://xdebug.org/donate.php
twitter: @derickr and @xdebug
Posted with an email client that doesn't mangle email: alpine
Hi!
<snip>While investigating an issue with Xdebug and fast_call/fast_ret, I
noticed that the opcodes "associated" with the "finally" statement, are
rolled up in the previous line.
The FAST_CALL/JMP instructions should really be linked to line 10.
Not doing so produces confusing results while doing code coverage,
where it now shows that the "echo catch2\n" on line 9 is executed.
This is of course not the case, but the linking of FAST_CALL and JMP
to line 9 makes it look like this. Is it possible to change this so
thta the FAST_CALL and JMP are linked to line 10 instead?Yeah, you're right. Should be fixed with
https://github.com/php/php-src/commit/b3afeeabefc4777ec4797a7e2c3688e9e20be4cc
.
Thanks — I'll give that a shot tomorrow. Did you merge that to 7.0 too?
And secondly, I am struggeling with where FAST_CALL and FAST_RET can
jump to. Right now, I have:+#if
PHP_VERSION_ID
>= 50500
} else if (opcode.opcode == ZEND_FAST_CALL) {
+#if
PHP_VERSION_ID
>= 70000
*jmp1 = VLD_ZNODE_JMP_LINE(opcode.op1, position, base_address);
+#else
*jmp1 = ((long) VLD_ZNODE_ELEM(opcode.op1, jmp_addr) - (long) base_address) / sizeof(zend_op);
+#endif
if (opcode.extended_value) {
*jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
}
return 1;
} else if (opcode.opcode == ZEND_FAST_RET) {
*jmp1 = position + 1;
if (opcode.extended_value) {
*jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
}
return 1;
+#endif
Which seems to work, although I am unsure about the "+ 1" for FAST_RET.
For PHP 7 only: FAST_CALL always jumps to op1. op2 is not a jmp addr, it's
a try_catch_array offset. For FAST_RET there are no jump addresses encoded
in the opline. It will either jump back to one past the invoking FAST_CALL
(of which there may be multiple), or (if finally is executed due to an
uncaught exception) it will jump back to the next applicable catch or
finally or leave the function.
Hmm, that's more complicated than I thought. How would I fix that code?
(PR welcome :D )
cheers,
Derick
Hi!
<snip>While investigating an issue with Xdebug and fast_call/fast_ret, I
noticed that the opcodes "associated" with the "finally" statement, are
rolled up in the previous line.The FAST_CALL/JMP instructions should really be linked to line 10.
Not doing so produces confusing results while doing code coverage,
where it now shows that the "echo catch2\n" on line 9 is executed.
This is of course not the case, but the linking of FAST_CALL and JMP
to line 9 makes it look like this. Is it possible to change this so
thta the FAST_CALL and JMP are linked to line 10 instead?Yeah, you're right. Should be fixed with
https://github.com/php/php-src/commit/b3afeeabefc4777ec4797a7e2c3688e9e20be4cc
.
Thanks — I'll give that a shot tomorrow. Did you merge that to 7.0 too?
And secondly, I am struggeling with where FAST_CALL and FAST_RET can
jump to. Right now, I have:+#if
PHP_VERSION_ID
>= 50500
} else if (opcode.opcode == ZEND_FAST_CALL) {
+#if
PHP_VERSION_ID
>= 70000
*jmp1 = VLD_ZNODE_JMP_LINE(opcode.op1, position,
base_address);
+#else
*jmp1 = ((long) VLD_ZNODE_ELEM(opcode.op1, jmp_addr) -
(long) base_address) / sizeof(zend_op);
+#endif
if (opcode.extended_value) {
*jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
}
return 1;
} else if (opcode.opcode == ZEND_FAST_RET) {
*jmp1 = position + 1;
if (opcode.extended_value) {
*jmp2 = VLD_ZNODE_ELEM(opcode.op2, opline_num);
}
return 1;
+#endif
Which seems to work, although I am unsure about the "+ 1" for FAST_RET.
For PHP 7 only: FAST_CALL always jumps to op1. op2 is not a jmp addr,
it's
a try_catch_array offset. For FAST_RET there are no jump addresses
encoded
in the opline. It will either jump back to one past the invoking
FAST_CALL
(of which there may be multiple), or (if finally is executed due to an
uncaught exception) it will jump back to the next applicable catch or
finally or leave the function.Hmm, that's more complicated than I thought. How would I fix that code?
(PR welcome :D )
I don't know what you need this for, but maybe you can use the same "good
enough" approximation we use for our internal control flow analysis, namely
simply say that FAST_CALL either jumps to op1 or to the next instruction,
while FAST_RET is treated as a terminator instruction (like RETURN). This
does not accurately model control flow, but it may be sufficient for your
purposes.
Nikita
For PHP 7 only: FAST_CALL always jumps to op1. op2 is not a jmp
addr, it's a try_catch_array offset. For FAST_RET there are no
jump addresses encoded in the opline. It will either jump back to
one past the invoking FAST_CALL (of which there may be multiple),
or (if finally is executed due to an uncaught exception) it will
jump back to the next applicable catch or finally or leave the
function.Hmm, that's more complicated than I thought. How would I fix that
code? (PR welcome :D )I don't know what you need this for, but maybe you can use the same
"good enough" approximation we use for our internal control flow
analysis, namely simply say that FAST_CALL either jumps to op1 or to
the next instruction, while FAST_RET is treated as a terminator
instruction (like RETURN). This does not accurately model control
flow, but it may be sufficient for your purposes.
I need to analyse every possible branch, and path through the code in
order to do dead code analysis (for which I think your simplified
option works), but also for path coverage, for which I don't think your
algorithm works. Xinchen alluded on IRC:
15:21 <@laruence> Derick, it used for calling "finally block"
15:22 <@laruence> it has the entry opline of finally block as opline->op1
15:22 <@laruence> and in the end of finally block ,there always be a
ZEND_FAST_RET opline, which will return to proper address
after finally block codes is executed
To me that indicates that the FAST_RET can jump back to any location
where FAST_CALL was called from (+1)?
In any case, I think my current code does what you describe, except for
FAST_RET, as that does not act like a RETURN - is there any reason why
my approach wouldn't work?
Some more observations and questions:
FAST_CALL:
*jmp1 = XDEBUG_ZNODE_JMP_LINE(opcode.op1, position, base_address);
-
But what do do for *jmp2 - if extended_value is set?
-
I can't come up with PHP code that sets extended_value though. Can you?
FAST_RET:
*jmp1 = position + 1;
-
But you indicate that that really should be *jmp1 = XDEBUG_JMP_EXIT?
-
Or, it should jump back to something else?
-
But how do I know to which possible opline(s) it is going to jump back to?
-
Is there a list of these somewhere?
-
I can't come up with PHP code that sets extended_value here either.
Can you?
cheers,
Derick
Hey:
For PHP 7 only: FAST_CALL always jumps to op1. op2 is not a jmp
addr, it's a try_catch_array offset. For FAST_RET there are no
jump addresses encoded in the opline. It will either jump back to
one past the invoking FAST_CALL (of which there may be multiple),
or (if finally is executed due to an uncaught exception) it will
jump back to the next applicable catch or finally or leave the
function.Hmm, that's more complicated than I thought. How would I fix that
code? (PR welcome :D )I don't know what you need this for, but maybe you can use the same
"good enough" approximation we use for our internal control flow
analysis, namely simply say that FAST_CALL either jumps to op1 or to
the next instruction, while FAST_RET is treated as a terminator
instruction (like RETURN). This does not accurately model control
flow, but it may be sufficient for your purposes.I need to analyse every possible branch, and path through the code in
order to do dead code analysis (for which I think your simplified
option works), but also for path coverage, for which I don't think your
algorithm works. Xinchen alluded on IRC:15:21 <@laruence> Derick, it used for calling "finally block"
15:22 <@laruence> it has the entry opline of finally block as opline->op1
15:22 <@laruence> and in the end of finally block ,there always be a
ZEND_FAST_RET opline, which will return to proper address
after finally block codes is executedTo me that indicates that the FAST_RET can jump back to any location
where FAST_CALL was called from (+1)?In any case, I think my current code does what you describe, except for
FAST_RET, as that does not act like a RETURN - is there any reason why
my approach wouldn't work?Some more observations and questions:
FAST_CALL:
*jmp1 = XDEBUG_ZNODE_JMP_LINE(opcode.op1, position, base_address);
- But what do do for *jmp2 - if extended_value is set?
if extended_value is set(FROM_FINALLY) that means this finally block is
inside another finally block
I am not sure what *jmp2 means here? ZEND_FAST_CALL always jmp to one
place. but in case FROM_FINALLY it has outer FAST_CALL's opline in op2 .
- I can't come up with PHP code that sets extended_value though. Can you?
try {
} finally {
try {
} finally /* this fast_call will set with FROM_FINALLY*/
{}
}
FAST_RET:
*jmp1 = position + 1;
But you indicate that that really should be *jmp1 = XDEBUG_JMP_EXIT?
Or, it should jump back to something else?
But how do I know to which possible opline(s) it is going to jump back
to?
- Is there a list of these somewhere?
I am afarid there is not, let me try to summary it(I will try to make it
clean, but mine poor english :<):
ZEND_FAST_CALL opcode is used for set a proper return address for
ZEND_FAST_RET.
there are two ways can entry a finally block, ZEND_FAST_CALL and
ZEND_HANDLE_EXCEPTION:
- ZEND_FAST_CALL: it set the return address to it's self (it will always
follow by a JMP, which will JMP to end of finall block). - ZEND_HANDLE_EXCEPTION, if there is catch block then jmp to it(in the end
of the catch block there will be a JMP to finally block), if no catch block
is matched and there is a finally block, then set fast_call_var->u2.lineno
== -1, and stash the EG(exception) into Z_OBJ_P(fast_call_var), then jmp to
the finally block.
for FAST_RET:
in fast_ret, if fast_call_var->u2.lineno != -1, then it means a jmp
address, simply jmp to it.
if fast_call->u2.lineno == -1, then there must be a un-handle
exception(stored in Z_OBJ_P(fast_call_var)), so:
if RET_TO_FINALLY(see blow), means there is another finally block needs
to be run, jmp to the ZEND_FAST_CALL opline of that finally block, in
which opline handle, if opline->extended_value
== ZEND_FAST_CALL_FROM_FINALLY(see below) and obviously current
Z_OBJ_P(fast_call_var) is set, then goes into the finally block behavior
like the way ZEND_HANDLE_EXCEPTION did, which is set the
fast_call->u2.lineno to -1, and jmp into the finally block.
otherwise, restore the exception, then goes like a new exception is
throw(jmp to a CATCH block or leave current function which will result
ZEND_HANDLE_EXCEPTION again).
- I can't come up with PHP code that sets extended_value here either.
try {
try {
} finally {
} /* this fast_ret will set to RET_TO_CATCH */
} catch () {
}
try {
try {
} finally {
} /* this fast_ret will set to RET_TO_FINALLY */
} finally {
}
thanks
Can you?
thanks
cheers,
Derick
--
Xinchen Hui
@Laruence
http://www.laruence.com/