diff --git a/.gitignore b/.gitignore index dd8330f3e..22c3f7454 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ /artifacts .DS_Store .idea + +\.vscode diff --git a/lib/emscripten/emtests/test_execvp.c b/lib/emscripten/emtests/test_execvp.c new file mode 100644 index 000000000..402654721 --- /dev/null +++ b/lib/emscripten/emtests/test_execvp.c @@ -0,0 +1,17 @@ +#include +#include + +int main() { + char command[] = "touch"; + char arg1[] = "foo.txt"; + char* argv[3]; + argv[0] = command; + argv[1] = arg1; + argv[2] = 0; + + printf("_execvp\n"); + int result = execvp(command, argv); + // should not return, and not print this message + printf("error"); + return 0; +} diff --git a/lib/emscripten/emtests/test_execvp.out b/lib/emscripten/emtests/test_execvp.out new file mode 100644 index 000000000..5048f1e2c --- /dev/null +++ b/lib/emscripten/emtests/test_execvp.out @@ -0,0 +1 @@ +_execvp diff --git a/lib/emscripten/emtests/test_execvp.wasm b/lib/emscripten/emtests/test_execvp.wasm new file mode 100644 index 000000000..a0959cc56 Binary files /dev/null and b/lib/emscripten/emtests/test_execvp.wasm differ diff --git a/lib/emscripten/emtests/test_execvp_windows.c b/lib/emscripten/emtests/test_execvp_windows.c new file mode 100644 index 000000000..c3920a4fa --- /dev/null +++ b/lib/emscripten/emtests/test_execvp_windows.c @@ -0,0 +1,18 @@ +#include +#include + +int main() { + char command[] = "C:\\Windows\\System32\\cmd.exe"; + char arg1[] = "echo"; + char arg2[] = "foo"; + char* argv[4]; + argv[0] = command; + argv[1] = arg1; + argv[2] = arg2; + argv[3] = 0; + printf("_execvp\n"); + int result = execvp(command, argv); + // should not return, and not print this message + printf("error"); + return 0; +} diff --git a/lib/emscripten/emtests/test_execvp_windows.wasm b/lib/emscripten/emtests/test_execvp_windows.wasm new file mode 100644 index 000000000..ec3a210c5 Binary files /dev/null and b/lib/emscripten/emtests/test_execvp_windows.wasm differ diff --git a/lib/emscripten/emtests/test_pipe.c b/lib/emscripten/emtests/test_pipe.c new file mode 100644 index 000000000..c8f650310 --- /dev/null +++ b/lib/emscripten/emtests/test_pipe.c @@ -0,0 +1,13 @@ +#include +#include +#include +#include + +int main() { + int mypipe[2]; + printf("pipe\n"); + if (pipe(mypipe)) { + printf("error\n"); + } + return 0; +} diff --git a/lib/emscripten/emtests/test_pipe.out b/lib/emscripten/emtests/test_pipe.out new file mode 100644 index 000000000..6b14dd0e0 --- /dev/null +++ b/lib/emscripten/emtests/test_pipe.out @@ -0,0 +1 @@ +pipe diff --git a/lib/emscripten/emtests/test_pipe.wasm b/lib/emscripten/emtests/test_pipe.wasm new file mode 100644 index 000000000..1aa68a5c4 Binary files /dev/null and b/lib/emscripten/emtests/test_pipe.wasm differ diff --git a/lib/emscripten/src/exec.rs b/lib/emscripten/src/exec.rs new file mode 100644 index 000000000..f47f09bf0 --- /dev/null +++ b/lib/emscripten/src/exec.rs @@ -0,0 +1,40 @@ +use libc::execvp as libc_execvp; +use std::cell::Cell; +use std::ffi::CString; +use wasmer_runtime_core::vm::Ctx; + +pub fn execvp(ctx: &mut Ctx, command_name_offset: u32, argv_offset: u32) -> i32 { + // a single reference to re-use + let emscripten_memory = ctx.memory(0); + + // read command name as string + let command_name_string_vec: Vec = emscripten_memory.view() + [(command_name_offset as usize)..] + .iter() + .map(|cell| cell.get()) + .take_while(|&byte| byte != 0) + .collect(); + let command_name_string = CString::new(command_name_string_vec).unwrap(); + + // get the array of args + let mut argv: Vec<*const i8> = emscripten_memory.view()[((argv_offset / 4) as usize)..] + .iter() + .map(|cell: &Cell| cell.get()) + .take_while(|&byte| byte != 0) + .map(|offset| { + let p: *const i8 = (emscripten_memory.view::()[(offset as usize)..]) + .iter() + .map(|cell| cell.as_ptr() as *const i8) + .collect::>()[0]; + p + }) + .collect(); + + // push a nullptr on to the end of the args array + argv.push(std::ptr::null()); + + // construct raw pointers and hand them to `execvp` + let command_pointer = command_name_string.as_ptr() as *const i8; + let args_pointer = argv.as_ptr(); + unsafe { libc_execvp(command_pointer, args_pointer) } +} diff --git a/lib/emscripten/src/exit.rs b/lib/emscripten/src/exit.rs new file mode 100644 index 000000000..d3f57a7a2 --- /dev/null +++ b/lib/emscripten/src/exit.rs @@ -0,0 +1,7 @@ +use wasmer_runtime_core::vm::Ctx; + +// __exit +pub fn exit(_ctx: &mut Ctx, value: i32) { + debug!("emscripten::exit {}", value); + ::std::process::exit(value); +} diff --git a/lib/emscripten/src/lib.rs b/lib/emscripten/src/lib.rs index 5a2f24211..20ff81553 100644 --- a/lib/emscripten/src/lib.rs +++ b/lib/emscripten/src/lib.rs @@ -31,6 +31,8 @@ mod emscripten_target; mod env; mod errno; mod exception; +mod exec; +mod exit; mod io; mod jmp; mod linking; @@ -439,6 +441,12 @@ pub fn generate_emscripten_env(globals: &mut EmscriptenGlobals) -> ImportObject "___unlock" => func!(crate::lock::___unlock), "___wait" => func!(crate::lock::___wait), + // exec + "_execvp" => func!(crate::exec::execvp), + + // exit + "__exit" => func!(crate::exit::exit), + // Env "___assert_fail" => func!(crate::env::___assert_fail), "_getenv" => func!(crate::env::_getenv), @@ -481,6 +489,7 @@ pub fn generate_emscripten_env(globals: &mut EmscriptenGlobals) -> ImportObject "___syscall39" => func!(crate::syscalls::___syscall39), "___syscall38" => func!(crate::syscalls::___syscall38), "___syscall40" => func!(crate::syscalls::___syscall40), + "___syscall42" => func!(crate::syscalls::___syscall42), "___syscall54" => func!(crate::syscalls::___syscall54), "___syscall57" => func!(crate::syscalls::___syscall57), "___syscall60" => func!(crate::syscalls::___syscall60), diff --git a/lib/emscripten/src/syscalls/mod.rs b/lib/emscripten/src/syscalls/mod.rs index 7c5ee578b..39bd2750d 100644 --- a/lib/emscripten/src/syscalls/mod.rs +++ b/lib/emscripten/src/syscalls/mod.rs @@ -40,6 +40,7 @@ use libc::{ use wasmer_runtime_core::vm::Ctx; use super::env; +use std::cell::Cell; use std::slice; // use std::sys::fd::FileDesc; @@ -140,6 +141,33 @@ pub fn ___syscall40(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int unsafe { rmdir(pathname_addr) } } +// pipe +pub fn ___syscall42(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_int { + debug!("emscripten::___syscall42 (pipe)"); + // offset to a file descriptor, which contains a read end and write end, 2 integers + let fd_offset: u32 = varargs.get(ctx); + + use std::cell::Cell; + let emscripten_memory = ctx.memory(0); + + // convert the file descriptor into a vec with two slots + let mut fd_vec: Vec = emscripten_memory.view()[((fd_offset / 4) as usize)..] + .iter() + .map(|pipe_end: &Cell| pipe_end.get()) + .take(2) + .collect(); + + // get it as a mutable pointer + let fd_ptr = fd_vec.as_mut_ptr(); + + // call pipe and store the pointers in this array + #[cfg(target_os = "windows")] + let result: c_int = unsafe { libc::pipe(fd_ptr, 2048, 0) }; + #[cfg(not(target_os = "windows"))] + let result: c_int = unsafe { libc::pipe(fd_ptr) }; + result +} + pub fn ___syscall60(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 { debug!("emscripten::___syscall60"); -1 diff --git a/lib/emscripten/tests/emtests/_common.rs b/lib/emscripten/tests/emtests/_common.rs index 8d3e00296..309d4de41 100644 --- a/lib/emscripten/tests/emtests/_common.rs +++ b/lib/emscripten/tests/emtests/_common.rs @@ -41,3 +41,37 @@ macro_rules! assert_emscripten_output { ); }}; } + +pub fn assert_emscripten_output(wasm_bytes: &[u8], raw_expected_str: &str) { + use wasmer_clif_backend::CraneliftCompiler; + use wasmer_emscripten::{generate_emscripten_env, stdio::StdioCapturer, EmscriptenGlobals}; + + let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &CraneliftCompiler::new()) + .expect("WASM can't be compiled"); + + let mut emscripten_globals = EmscriptenGlobals::new(&module); + let import_object = generate_emscripten_env(&mut emscripten_globals); + let mut instance = module + .instantiate(&import_object) + .map_err(|err| format!("Can't instantiate the WebAssembly module: {:?}", err)) + .unwrap(); + + let capturer = StdioCapturer::new(); + + wasmer_emscripten::run_emscripten_instance(&module, &mut instance, "test", vec![]) + .expect("run_emscripten_instance finishes"); + + let raw_output_string = capturer.end().unwrap().0; + + // trim the strings to avoid cross-platform line ending and white space issues + let output = raw_output_string.trim(); + let expected_output = raw_expected_str.trim(); + + let contains_output = output.contains(expected_output); + + assert!( + contains_output, + "Output: `{}` does not contain expected output: `{}`", + output, expected_output + ); +} diff --git a/lib/emscripten/tests/emtests/mod.rs b/lib/emscripten/tests/emtests/mod.rs index a280fc48a..89a66bf2b 100644 --- a/lib/emscripten/tests/emtests/mod.rs +++ b/lib/emscripten/tests/emtests/mod.rs @@ -43,6 +43,7 @@ mod test_exceptions_2; mod test_exceptions_multi; mod test_exceptions_std; mod test_exceptions_white_list; +mod test_execvp; mod test_fast_math; mod test_flexarray_struct; mod test_float32_precise; @@ -118,6 +119,7 @@ mod test_nested_struct_varargs; mod test_nl_types; mod test_perrar; mod test_phiundef; +mod test_pipe; mod test_poll; mod test_posixtime; mod test_printf_2; diff --git a/lib/emscripten/tests/emtests/test_execvp.rs b/lib/emscripten/tests/emtests/test_execvp.rs new file mode 100644 index 000000000..c9b4b5113 --- /dev/null +++ b/lib/emscripten/tests/emtests/test_execvp.rs @@ -0,0 +1,17 @@ +#[test] +fn test_execvp() { + #[cfg(not(target_os = "windows"))] + assert_emscripten_output!( + "../../emtests/test_execvp.wasm", + "test_execvp", + vec![], + "../../emtests/test_execvp.out" + ); + #[cfg(target_os = "windows")] + assert_emscripten_output!( + "../../emtests/test_execvp_windows.wasm", + "test_execvp", + vec![], + "../../emtests/test_execvp.out" + ); +} diff --git a/lib/emscripten/tests/emtests/test_pipe.rs b/lib/emscripten/tests/emtests/test_pipe.rs new file mode 100644 index 000000000..9ffe0c828 --- /dev/null +++ b/lib/emscripten/tests/emtests/test_pipe.rs @@ -0,0 +1,8 @@ +use crate::emtests::_common::assert_emscripten_output; + +#[test] +fn test_pipe() { + let wasm_bytes = include_bytes!("../../emtests/test_pipe.wasm"); + let expected_str = include_str!("../../emtests/test_pipe.out"); + assert_emscripten_output(wasm_bytes, expected_str); +}