CopernicaMarketingSoftware / PHP-CPP

Library to build PHP extensions with C++
http://www.php-cpp.com/
Apache License 2.0
1.43k stars 333 forks source link

Access violation on eval PHP code #320

Open atvise opened 7 years ago

atvise commented 7 years ago

Hi,

i've compiled the latest version of PHP-CPP (a5ca2f39c43b3bc67f62f73863a5c0c8beb45b2e) with PHP 7.1.3 (64bit) on Win10 (64bit) VS2015. When I call the function Php::eval(phpContent) -> I get following error

script.cpp (Line 85) -> opcodes.h (Line 76) -> ExecuteState execState(0) 0xC0000005: Access violation reading location 0x0000000000000010

Can you verify this problem ?

Thank you very much for your help in advance!

Greetings, atvise

atvise commented 7 years ago

This also happens with PHP 7.0.17

sjinks commented 7 years ago

Do you happen to have a small test case which reproduces the issue?

atvise commented 7 years ago

Yes here you go:

#include <phpcpp/phpcpp.h>

extern "C" {
  __declspec(dllexport) void* get_module()
  {
    static Php::Extension myExtension("php_my_test", "1.0");
    static bool extensionAdded = false;

    if (!extensionAdded)
    {
      extensionAdded = true;
      myExtension.onRequest([]()
      {
        Php::eval("echo 'Hello World!';");
      });
    }

    return myExtension;
  }
}
sjinks commented 7 years ago

Confirmed.

Program received signal SIGSEGV, Segmentation fault.
0x00007fffed400e94 in Php::ExecuteState::ExecuteState (this=0x7fffffffc370, no_extensions=0) at zend/executestate.h:55
55              _return_value  = EG(current_execute_data)->return_value;
(gdb) bt
#0  0x00007fffed400e94 in Php::ExecuteState::ExecuteState (this=0x7fffffffc370, no_extensions=0) at zend/executestate.h:55
#1  0x00007fffed401120 in Php::Opcodes::execute (this=0x5555569d5fd0) at zend/opcodes.h:76
#2  0x00007fffed4006ec in Php::Script::execute (this=0x7fffffffc430) at zend/script.cpp:85
#3  0x00007fffed3feaa3 in Php::eval (phpCode=0x7fffed6655da "echo 'Hello World!';") at zend/eval.cpp:27
#4  0x00007fffed66506e in get_module::{lambda()#1}::operator()() const () from ./test.so
#5  0x00007fffed6652fa in std::_Function_handler<void (), get_module::{lambda()#1}>::_M_invoke(std::_Any_data const&) () from ./test.so
#6  0x00007fffed3f6498 in std::function<void ()>::operator()() const (this=0x55555686dfc0) at /usr/include/c++/6/functional:2136
#7  0x00007fffed3f39da in Php::ExtensionImpl::processRequest (type=1, module_number=49) at zend/extensionimpl.cpp:152
#8  0x0000555555da13cb in zend_activate_modules () at /tmp/php-build/source/7.0.17/Zend/zend_API.c:2541
#9  0x0000555555ccd595 in php_request_startup () at /tmp/php-build/source/7.0.17/main/main.c:1627
#10 0x0000555555e6e3fc in do_cli (argc=6, argv=0x5555567f9ff0) at /tmp/php-build/source/7.0.17/sapi/cli/php_cli.c:948
#11 0x0000555555e6faa3 in main (argc=6, argv=0x5555567f9ff0) at /tmp/php-build/source/7.0.17/sapi/cli/php_cli.c:1347
sjinks commented 7 years ago

The issue is that you are trying to execute PHP code before executor data are set up.

This will work:

#include <phpcpp.h>

static void testfunc()
{
    Php::eval("echo 'Hello World!';");
}

extern "C" {
  void* get_module()
  {
    static Php::Extension myExtension("php_my_test", "1.0");
    myExtension.add<testfunc>("testfunc");
    return myExtension;
  }
}
php -n -dextension_dir=. -dextension=test.so -r 'testfunc();'
sjinks commented 7 years ago

If you absolutely must call PHP code during request startup but before the executor is initialized, you will have to use Zend API:

#include <phpcpp.h>
#include <cstring>

#include <Zend/zend.h>
#include <Zend/zend_compile.h>
#include <Zend/zend_execute.h>

extern "C" {
  void* get_module()
  {
    static Php::Extension myExtension("php_my_test", "1.0");
    static bool extensionAdded = false;

    if (!extensionAdded)
    {
      extensionAdded = true;
      myExtension.onRequest([]()
      {
          char code[] = "echo \"Hello, world!\\n\";";
          char* desc  = zend_make_compiled_string_description("eval'd code");
          zend_eval_stringl_ex(code, std::strlen(code), nullptr, desc, 1);
          efree(desc);
      });
    }

    return myExtension;
  }
}

Simple wrapper function:

void runPhpCode(const std::string& code)
{
    char* desc = zend_make_compiled_string_description("eval'd code");
    zend_eval_stringl_ex(const_cast<char*>(code.c_str()), code.size(), nullptr, desc, 1);
    efree(desc);
}
atvise commented 7 years ago

@sjinks Thank you very much for your effort and help !! I'm very appreciating it! But I'm wondering why this is not working with Php::eval() because this all worked in the legacy version of PHP-CPP and PHP 5.6 :(

Don't get me wrong your code works great but doesn't it make the existing implementation of PHP-CPP obsolete ? I think we should somehow get it running again with the PHP-CPP eval implementation but I'm lacking in knowlege how PHP is working in that way :(

sjinks commented 7 years ago

Php::eval still works, the only issue is that it should not be called from onRequest() callback (RINIT happens before the script to run is parsed).

onRequest/RINIT is usually used to initialize some data structures etc, not to run the code.

Php::eval tries to save executor's state (see ExecuteState::ExecuteState()), rebuild the active symbol table (see Opcodes::execute()) — this is what zend_eval_stringl() does not do. I am not sure, probably that code was copied from PHP5, or maybe it tried to generalize include/require/eval cases.

atvise commented 7 years ago

As my underestanding of this library everything will be initialized after onStartup Callback a mentioned in the documentation:

"The startup callback is called when the Zend engine has loaded your extension and all functions and classes in it were registered. If you want to initialize additional variables in your extension before the functions are going to get called, you can use the onStartup() function and register a callback to run this initialization code.

After the Zend engine is initialized, it is ready to process requests. In the example above we used the onRequest() method to register a callback that is called in front of each request"

And as I mentioned before the same function Php::eval() (with nearly exact the same implementation behind) worked with PHP 5.6 and PHP-CPP Lagacy in the onRequest() method.

rodriguescr commented 6 years ago

Hi,

Sorry to bump this post, but @sjinks i'm trying to use your solution, but can't get it to work. I've downloaded php source to import zend libraries, but with no sucess to compile. I keep getting this error:

~/.../Examples/Extension >>> make                                                                                                                                                                                                                               ±[●●][master]
g++ -Wall -c -O2 -std=c++11 -fpic -o extension.o extension.cpp
In file included from php7/Zend/zend_types.h:27,
                 from php7/Zend/zend.h:29,
                 from extension.cpp:4:
php7/Zend/zend_portability.h:45:11: fatal error: zend_config.h: Arquivo ou diretório inexistente
 # include <zend_config.h>
           ^~~~~~~~~~~~~~~
compilation terminated.
make: *** [Makefile:127: extension.o] Error 1

Could you please help me?

sjinks commented 6 years ago
g++ $(php-config --includes) -Wall -c -O2 -std=c++11 -fpic -o extension.o extension.cpp
imagemlt commented 5 years ago

@sjinks hi,I used your way but it turned to error in linking process:

g++ -I/usr/include/php -I/usr/include/php/main -I/usr/include/php/TSRM -I/usr/include/php/Zend -I/usr/include/php/ext -I/usr/include/php/ext/date/lib -Wall -c -O2 -std=c++11 -fpic -o main.o main.cpp
g++ -shared -o imagecpp.so main.o -I/usr/include/php -I/usr/include/php/main -I/usr/include/php/TSRM -I/usr/include/php/Zend -I/usr/include/php/ext -I/usr/include/php/ext/date/lib -lphpcpp
Undefined symbols for architecture x86_64:
  "_zend_eval_stringl_ex", referenced from:
      std::__1::__function::__func<get_module::$_0, std::__1::allocator<get_module::$_0>, void ()>::operator()() in main.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [imagecpp.so] Error 1

Could you please help me?

sjinks commented 5 years ago

You need to link against libphp as well. Add smth like -lphp7 to your link command.

imagemlt commented 5 years ago

@sjinks it seems that there donnot exists such library

imagemlt commented 5 years ago

@sjinks solved by adding -undefined dynamic_lookup because I use Mac OS X