argument extracting basics

This commit is contained in:
Robin Appelman 2019-03-25 01:03:48 +01:00
commit 5b95e4370a
13 changed files with 5327 additions and 10 deletions

View file

@ -1,20 +1,38 @@
use std::intrinsics::transmute;
use ivory::*;
use ivory::externs::printf;
use ivory::zend::{ExecuteData, Value};
use ivory::zend::{ExecuteData, ZVal};
#[ivory_export]
fn hello_other(_other: String) {
printf(format!("Hello ",));
printf(format!("Hello ", ));
}
#[ivory_export]
fn hello_world() {
printf("Hello world, Rust!");
printf("Hello world, Rust2!");
}
#[no_mangle]
pub extern "C" fn dump(data: *const ExecuteData, retval: *mut ZVal) {
let data: &ExecuteData = unsafe { data.as_ref() }.unwrap();
let num = data.num_args();
for arg in data.args() {
printf(format!("{}\n", unsafe { arg.as_i64() }));
}
}
const FUNCTION_META_DUMP: ::ivory::zend::FunctionMeta = ::ivory::zend::FunctionMeta {
name: { concat!("dump", "\0").as_ptr() as *const ::libc::c_char },
func: dump,
args: &[],
};
ivory_module!({
name: "demo",
version: "0.0.1",
functions: &[hello_world, hello_other],
functions: &[hello_world, hello_other, dump],
info: &[("demo extension", "enabled")]
});

View file

@ -11,6 +11,7 @@ edition = "2018"
[dependencies]
libc = "0.2.50"
ivory-macro = { version = "0.1", path = "macro" }
ivory-sys = { version = "7.3", path = "sys" }
[lib]
name = "ivory"

View file

@ -53,7 +53,7 @@ fn export_fn(item: ItemFn) -> TokenStream {
quote! {
#[no_mangle]
pub extern "C" fn #name(data: &ExecuteData, retval: &Value) {
pub extern "C" fn #name(data: *const ::ivory::zend::ExecuteData, retval: *mut ::ivory::zend::ZVal) {
let result = #body;
}

4845
ivory/src/zend/bindings.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,8 @@
pub use self::module::*;
pub use self::function::*;
pub use self::zval::{ExecuteData, ZVal};
mod module;
mod function;
mod zval;
mod bindings;

View file

@ -4,6 +4,7 @@ use std::mem;
use libc::*;
use crate::zend::function::{Function, ArgInfo};
use crate::zend::{ExecuteData, ZVal};
pub(crate) type StartupFunc = extern "C" fn(type_: c_int, module_number: c_int) -> c_int;
pub(crate) type ShutdownFunc = extern "C" fn(type_: c_int, module_number: c_int) -> c_int;
@ -11,11 +12,7 @@ pub(crate) type InfoFunc = extern "C" fn();
pub(crate) type GlobalsCtorFunc = extern "C" fn(global: *const c_void) -> c_void;
pub(crate) type GlobalsDtorFunc = extern "C" fn(global: *const c_void) -> c_void;
pub(crate) type PostDeactivateFunc = extern "C" fn() -> c_int;
pub(crate) type HandlerFunc = extern "C" fn(execute_data: &ExecuteData, retval: &Value);
pub struct ExecuteData {}
pub struct Value {}
pub(crate) type HandlerFunc = extern "C" fn(execute_data: *const ExecuteData, retval: *mut ZVal);
pub struct ModuleDep {}

89
ivory/src/zend/zval.rs Normal file
View file

@ -0,0 +1,89 @@
use std::intrinsics::transmute;
use std::mem::size_of;
use crate::externs::printf;
use super::bindings::{zend_execute_data, zval};
#[derive(Clone)]
#[repr(transparent)]
pub struct ZVal(zval);
impl ZVal {
pub unsafe fn as_i64(&self) -> i64 {
self.0.value.lval as i64
//self.0.u1.type_info as i64
}
}
#[repr(transparent)]
pub struct ExecuteData(zend_execute_data);
impl ExecuteData {
pub fn num_args(&self) -> u32 {
unsafe { self.0.This.u2.num_args }
}
fn get_arg_base(&self) -> *const ZVal {
let offset = (size_of::<zend_execute_data>() + size_of::<zval>() - 1) / size_of::<zval>();
let self_ptr: *const zend_execute_data = &self.0;
unsafe {
transmute::<_, *const ZVal>(self_ptr).add(5)
}
}
pub unsafe fn get_arg(&self, i: u32) -> &ZVal {
unsafe {
let base = self.get_arg_base();
let val_ptr = base.add(i as usize);
&*val_ptr
}
}
pub fn args(&self) -> ArgIterator {
ArgIterator {
base: self.get_arg_base(),
count: self.num_args(),
item: 0,
}
}
}
pub struct ArgIterator {
base: *const ZVal,
count: u32,
item: u32,
}
impl Iterator for ArgIterator {
type Item = ZVal;
fn next(&mut self) -> Option<Self::Item> {
if self.item < self.count {
let val = unsafe { &*(self.base.add(self.item as usize)) };
self.item += 1;
Some((*val).clone())
} else {
None
}
}
}
#[repr(u8)]
enum ZValType {
Undef = 0,
Null = 1,
False = 2,
True = 3,
Long = 4,
Double = 5,
String = 6,
Array = 7,
Object = 8,
Resource = 9,
Reference = 10,
}
//impl From<ZVal> for Option<i64> {
// fn from(val: ZVal) -> Self {}
//}

13
ivory/sys/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
description = "Rust bindings for libphp"
name = "ivory-sys"
version = "7.3.3"
authors = ["Herman J. Radtke III <herman@hermanradtke.com>", "Heinz Gies <heinz@licenser.net>"]
links = "php7"
build = "build.rs"
license = "MIT"
[build-dependencies]
bindgen = "0.43"
cc = "1.0"
num_cpus="1.0"

48
ivory/sys/README.md Normal file
View file

@ -0,0 +1,48 @@
# php-sys
Bindings to php.
Note: I have only tested this without ZTS.
## PHP
In order to compile php-sys, we need development headers and the libphp7 library. That library may come in the form of `libphp7.so` or `libphp7.a` depending on how you install/compile PHP.
### From Package
* For Ubuntu, please refer to the [.travis.yml](../.travis.yml) _install_ section for the commands.
* For Mac OS X, I could not find a set of packages that worked.
### From Source
Some basic instructions on how to install PHP so you can embed it into Rust.
#### Mac OS X
I had to use brew to install bison. I believe autoconf and other tools were either already installed or provided by Mac OS X. Brew installed some modified version of libiconv which confused PHP. I also had some problems, so I stopped building xml related stuff. To build I had to do:
```
$ ./genfiles
$ ./buildconf --force
$ PATH="/usr/local/opt/bison/bin:$PATH" ./configure --enable-debug --enable-embed=static --without-iconv --disable-libxml --disable-dom --disable-xml --disable-simplexml --disable-xmlwriter --disable-xmlreader --without-pear
$ PATH="/usr/local/opt/bison/bin:$PATH" make
$ PATH="/usr/local/opt/bison/bin:$PATH" make test
```
Note: I embed a static library on Mac OS X. If you want to do embed PHP with a shared library, then use `--enable-embed=shared`.
#### Linux
Here are the dependencies needed (in apt-get form):
```bash
$ apt-get install git make gcc libxml2-dev autoconf bison valgrind clang re2c
```
```
$ ./genfiles
$ ./buildconf --force
$ ./configure --enable-debug --enable-embed=shared
$ make
$ make test
```

231
ivory/sys/build.rs Normal file
View file

@ -0,0 +1,231 @@
extern crate bindgen;
extern crate cc;
extern crate num_cpus;
use bindgen::callbacks::{MacroParsingBehavior, ParseCallbacks};
use bindgen::Builder;
use std::collections::HashSet;
use std::env;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::sync::{Arc, RwLock};
const PHP_VERSION: &'static str = concat!("php-", env!("CARGO_PKG_VERSION"));
/// println_stderr and run_command_or_fail are copied from rdkafka-sys
macro_rules! println_stderr(
($($arg:tt)*) => { {
let r = writeln!(&mut ::std::io::stderr(), $($arg)*);
r.expect("failed printing to stderr");
} }
);
fn run_command_or_fail(dir: String, cmd: &str, args: &[&str]) {
println_stderr!(
"Running command: \"{} {}\" in dir: {}",
cmd,
args.join(" "),
dir
);
let ret = Command::new(cmd).current_dir(dir).args(args).env("CC", "clang").status();
match ret.map(|status| (status.success(), status.code())) {
Ok((true, _)) => return,
Ok((false, Some(c))) => panic!("Command failed with error code {}", c),
Ok((false, None)) => panic!("Command got killed"),
Err(e) => panic!("Command failed with error: {}", e),
}
}
fn target(path: &str) -> String {
let osdir = env::var("PWD").unwrap();
let pfx = match env::var("CARGO_TARGET_DIR") {
Ok(d) => d,
Err(_) => String::from("target"),
};
let profile = env::var("PROFILE").unwrap();
format!("{}/{}/{}/native/{}", osdir, pfx, profile, path)
}
fn exists(path: &str) -> bool {
Path::new(target(path).as_str()).exists()
}
/// This is needed to prevent bindgen to create multiple definitions of the same macro and fail
#[derive(Debug)]
struct MacroCallback {
macros: Arc<RwLock<HashSet<String>>>,
}
impl ParseCallbacks for MacroCallback {
fn will_parse_macro(&self, name: &str) -> MacroParsingBehavior {
self.macros.write().unwrap().insert(name.into());
match name {
"FP_NAN" | "FP_INFINITE" | "FP_ZERO" | "FP_SUBNORMAL" | "FP_NORMAL" => {
MacroParsingBehavior::Ignore
}
_ => MacroParsingBehavior::Default,
}
}
}
fn main() {
let cpus = format!("{}", num_cpus::get());
#[cfg(all(target_os = "linux"))]
let default_link_static = false;
#[cfg(all(target_os = "macos"))]
let default_link_static = true;
let php_version = option_env!("PHP_VERSION").unwrap_or(PHP_VERSION);
let macros = Arc::new(RwLock::new(HashSet::new()));
println!("cargo:rerun-if-env-changed=PHP_VERSION");
println!("cargo:rerun-if-env-changed=PHP_LINK_STATIC");
let link_dynamic = env::var_os("PHP_LINK_DYNAMIC")
.map(|_| true)
.unwrap_or(false);
let link_static = env::var_os("PHP_LINK_STATIC")
.map(|_| true)
.unwrap_or(default_link_static && !link_dynamic);
if !exists("php-src/LICENSE") {
println_stderr!("Setting up PHP {}", php_version);
run_command_or_fail(
"/".to_string(),
"mkdir",
&[
"-p",
&target("")
],
);
run_command_or_fail(
target(""),
"git",
&[
"clone",
"https://github.com/php/php-src",
format!("--branch={}", php_version).as_str(),
],
);
run_command_or_fail(
target("php-src"),
"sed",
&[
"-e",
"s/void zend_signal_startup/ZEND_API void zend_signal_startup/g",
"-ibk",
"Zend/zend_signal.c",
"Zend/zend_signal.h",
],
);
run_command_or_fail(target("php-src"), "./genfiles", &[]);
run_command_or_fail(target("php-src"), "./buildconf", &["--force"]);
let embed_type = if link_static { "static" } else { "shared" };
#[cfg(all(target_os = "linux"))]
let config = &[
"--enable-debug",
&format!("--enable-embed={}", embed_type),
"--disable-cli",
"--disable-cgi",
"--enable-maintainer-zts",
// "--without-iconv",
"--disable-libxml",
"--disable-dom",
"--disable-xml",
"--disable-simplexml",
"--disable-xmlwriter",
"--disable-xmlreader",
// "--without-pear",
// "--with-libdir=lib64",
// "--with-pic",
];
#[cfg(all(target_os = "macos"))]
let config = &[
"--enable-debug",
&format!("--enable-embed={}", embed_type),
"--disable-cli",
"--disable-cgi",
"--enable-maintainer-zts",
"--without-iconv",
"--disable-libxml",
"--disable-dom",
"--disable-xml",
"--disable-simplexml",
"--disable-xmlwriter",
"--disable-xmlreader",
// "--without-pear",
// "--with-libdir=lib64",
// "--with-pic",
];
run_command_or_fail(target("php-src"), "./configure", config);
run_command_or_fail(target("php-src"), "make", &["-j", cpus.as_str()]);
}
let include_dir = target("php-src");
let lib_dir = target("php-src/libs");
let link_type = if link_static { "=static" } else { "" };
println!("cargo:rustc-link-lib{}=php7", link_type);
println!("cargo:rustc-link-search=native={}", lib_dir);
let includes = ["/", "/TSRM", "/Zend", "/main"]
.iter()
.map(|d| format!("-I{}{}", include_dir, d))
.collect::<Vec<String>>();
let bindings = Builder::default()
.rustfmt_bindings(true)
.clang_args(includes)
.whitelist_function("_zend_file_handle__bindgen_ty_1")
.whitelist_function("php_execute_script")
.whitelist_function("php_module_startup")
.whitelist_function("php_request_shutdown")
.whitelist_function("php_request_startup")
.whitelist_function("phprpm_fopen")
.whitelist_function("sapi_send_headers")
.whitelist_function("sapi_startup")
.whitelist_function("sg_request_info")
.whitelist_function("sg_sapi_headers")
.whitelist_function("sg_server_context")
.whitelist_function("sg_server_context")
.whitelist_function("sg_set_server_context")
.whitelist_function("sg_set_server_context")
.whitelist_function("ts_resource_ex")
.whitelist_function("tsrm_startup")
.whitelist_function("zend_error")
.whitelist_function("zend_signal_startup")
.whitelist_function("zend_tsrmls_cache_update")
.whitelist_var("SAPI_HEADER_SENT_SUCCESSFULLY")
.whitelist_type("sapi_headers_struc")
.whitelist_type("sapi_module_struc")
.whitelist_type("sapi_request_info")
.whitelist_type("ZEND_RESULT_CODE")
.whitelist_type("zval")
.whitelist_type("zend_execute_data")
.whitelist_var("zend_stream_type_ZEND_HANDLE_FP")
.parse_callbacks(Box::new(MacroCallback {
macros: macros.clone(),
})).derive_default(true)
.header("wrapper.h")
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
cc::Build::new()
.file("src/shim.c")
.include(&include_dir)
.flag("-fPIC")
.flag("-m64")
.include(&format!("{}/TSRM", include_dir))
.include(&format!("{}/Zend", include_dir))
.include(&format!("{}/main", include_dir))
.compile("foo");
}

17
ivory/sys/src/lib.rs Normal file
View file

@ -0,0 +1,17 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use std::os::raw::{c_char, c_uchar, c_void};
//extern "C" {
//// pub fn sg_request_info() -> *mut sapi_request_info;
// pub fn sg_server_context() -> *mut c_void;
// pub fn sg_set_server_context(context: *mut c_void);
//// pub fn sg_sapi_headers() -> *mut sapi_headers_struct;
// pub fn sg_headers_sent() -> c_uchar;
// pub fn sg_set_headers_sent(is_sent: c_uchar);
// pub fn zend_tsrmls_cache_update();
//// pub fn phprpm_fopen(filename: *const c_char, mode: *const c_char) -> *mut FILE;
//}
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

48
ivory/sys/src/shim.c Normal file
View file

@ -0,0 +1,48 @@
/**
* This file defines some simple C functions to make ffi interop easier. The
* majority of the functions are wrapping some pre-processor define that is
* critical to PHP internals.
*/
#include <Zend/zend.h>
#include <main/php.h>
#include <sapi/embed/php_embed.h>
//sapi_request_info * sg_request_info() {
// return &SG(request_info);
//}
void * sg_server_context() {
return SG(server_context);
}
void sg_set_server_context(void *context) {
SG(server_context) = context;
}
//sapi_headers_struct * sg_sapi_headers() {
// return &SG(sapi_headers);
//}
unsigned char sg_headers_sent() {
return SG(headers_sent);
}
void sg_set_headers_sent(unsigned char is_sent) {
SG(headers_sent) = is_sent;
}
void zend_tsrmls_cache_update() {
ZEND_TSRMLS_CACHE_UPDATE();
}
/*
* Create a wrapper around fopen
*
* I tried to use libc, but libc defines FILE different from the php bindings
* generated by bindgen. Thus, it was easier to wrap this and expose it in Rust
* via an extern fn definition.
*/
FILE *phprpm_fopen(const char *filename, const char *mode) {
return fopen(filename, mode);
}

7
ivory/sys/wrapper.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef PHP_RS_WRAPPER_H
#define PHP_RS_WRAPPER_H
#include <Zend/zend.h>
#include <Zend/zend_compile.h>
//#include <main/php.h>
//#include <sapi/embed/php_embed.h>
#endif