├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── HOW_ITS_MADE.md ├── LICENSE ├── README.md ├── c-macro ├── .gitignore ├── Cargo.toml └── src │ └── lib.rs ├── runtime-c ├── .gitignore ├── Cargo.toml └── src │ └── lib.rs ├── rust-toolchain.toml └── test-app ├── .gitignore ├── Cargo.toml └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.c 3 | *.o 4 | *.bin -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "c-macro" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "proc-macro2", 10 | "quote", 11 | ] 12 | 13 | [[package]] 14 | name = "cc" 15 | version = "1.0.73" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 18 | 19 | [[package]] 20 | name = "libc" 21 | version = "0.2.119" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" 24 | 25 | [[package]] 26 | name = "page_size" 27 | version = "0.4.2" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" 30 | dependencies = [ 31 | "libc", 32 | "winapi", 33 | ] 34 | 35 | [[package]] 36 | name = "paste" 37 | version = "1.0.6" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" 40 | 41 | [[package]] 42 | name = "proc-macro2" 43 | version = "1.0.36" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 46 | dependencies = [ 47 | "unicode-xid", 48 | ] 49 | 50 | [[package]] 51 | name = "quote" 52 | version = "1.0.15" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 55 | dependencies = [ 56 | "proc-macro2", 57 | ] 58 | 59 | [[package]] 60 | name = "runtime-c" 61 | version = "0.1.0" 62 | dependencies = [ 63 | "page_size", 64 | "syscalls", 65 | ] 66 | 67 | [[package]] 68 | name = "syscalls" 69 | version = "0.5.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "a837f64f72473acde47fbd13431e83fc9081e52ae49ea1430003831536ed04d7" 72 | dependencies = [ 73 | "cc", 74 | "paste", 75 | ] 76 | 77 | [[package]] 78 | name = "test-app" 79 | version = "0.1.0" 80 | dependencies = [ 81 | "c-macro", 82 | "runtime-c", 83 | ] 84 | 85 | [[package]] 86 | name = "unicode-xid" 87 | version = "0.2.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 90 | 91 | [[package]] 92 | name = "winapi" 93 | version = "0.3.9" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 96 | dependencies = [ 97 | "winapi-i686-pc-windows-gnu", 98 | "winapi-x86_64-pc-windows-gnu", 99 | ] 100 | 101 | [[package]] 102 | name = "winapi-i686-pc-windows-gnu" 103 | version = "0.4.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 106 | 107 | [[package]] 108 | name = "winapi-x86_64-pc-windows-gnu" 109 | version = "0.4.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 112 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "runtime-c", 4 | "test-app", 5 | "c-macro" 6 | ] -------------------------------------------------------------------------------- /HOW_ITS_MADE.md: -------------------------------------------------------------------------------- 1 | # How it's made - a recipe for disaster 2 | So, you want to know how this hot mess works. Me too, to be honest, but I can explain. 3 | 4 | ## Ingredients 5 | - One cup `gcc` 6 | - A sprinkle of `proc_macro` 7 | - A pinch of `mprotect(1)` 8 | - Enough `objcopy` flour 9 | - Steep all in copious amounts of memory-unsafety sauce 10 | 11 | ## No, seriously. 12 | So, this has two parts to it. One part is the actual executor, the other part is the proc macro that enables this in the first place. 13 | 14 | ### The executor 15 | The executor is a sequential chain of crimes. This is what it does, roughly in the correct order: 16 | - It writes your source code to `temp_file.c` on the disk. 17 | - It invokes `gcc` on it to compile it. (It does not link it.) 18 | - It uses `objcopy` to obtain a flat binary containing just your code (not an ELF). 19 | - It allocates one megabyte of the finest page-aligned memory. 20 | - It reads the binary produced by `objcopy` into that buffer it allocated earlier. 21 | - It deletes all the files it created, because it has what it needs now. 22 | - It then uses `mprotect(1)` to mark it as readable, writable and executable in memory. 23 | - After all that's done, it jumps. 24 | 25 | As you can probably tell, this is possibly the single unsafest thing one can do in Rust. 26 | 27 | ### The proc macro 28 | The proc macro is what is responsible for making your C code behave. This one's a bit complex. 29 | 30 | It uses the Rust tokeniser to tokenise the C code (yes, this works in most cases.), then mostly-accurately reconstructs your original C source code from it, preserving whitespace to provide accurate error messages... ~~or it would if I didn't drop errors silently, anyway~~. This part of the code is mostly based on [Mara Bos](https://m-ou.se/)'s work on [inline-python](https://github.com/fusion-engineering/inline-python). Thanks for your [excellent blog series on how to do this](https://blog.m-ou.se/writing-python-inside-rust-1/), Mara! (FYI, this is also how [inline-lua](https://github.com/ry00001/inline-lua) works). 31 | 32 | During this phase, the reconstructor is on the lookout for `'` tokens (single quote). In Rust, this is normally used to specify lifetimes, so `'identifier` is kosher in the tokeniser's eyes. I use this fact to watch for them, and when I find one, I note it down, and replace it with `((void(*)()){})`. 33 | 34 | ### What? 35 | 36 | Okay, so there's a lot to unpack in `((void(*)()){})`. Let's try to break it down. 37 | 38 | The most of the parentheses come from [C's arcane function pointer syntax](https://www.cprogramming.com/tutorial/function-pointers.html). This encompasses everything except for the `{}`s, and is a cast to a function that takes no arguments and returns nothing. 39 | 40 | Now, onto the braces. This is by far the most crimey part of this entire project. 41 | 42 | ### The braces 43 | If you're familiar with Rust, you've probably immediately noticed one thing. 44 | 45 | `{}` is how you do a format string in Rust. 46 | 47 | At the end of the proc macro, you'd think I'd emit `compile_c("your code as a string literal")`. But I don't. What I actually emit is `compile_c(&format!("your code as a string literal", ))`. This is what the `{}` is for. 48 | 49 | I note down what the `something` in `'something` is, and this is where I use it. So, I set up a `Vec` consisting of `TokenStream`s, which is what `quote!` outputs, and I enumerate my list of references. For each one, I emit Rust code that looks like `((something as *const ()) as u64)`. 50 | 51 | There's a couple things to note here. First one is `u64`, meaning that it will probably only behave on 64-bit systems. Second one is `something`. Because of when macros run in Rust, I can't simply get the address of `something` then. So, what the macro does is write code that fetches that address at runtime, and format-strings it into the C source for you. (As a bonus element of cursed, this actually formats the pointer addresses as decimal, so you end up with `((void(*)())123456789)`.) 52 | 53 | I'm sorry, lmao. 54 | 55 | ## To wrap things up 56 | This is a horrible idea, do not try any of this at home. This is probably the most unsafe thing you can possibly do in Rust. This was made simply because I wanted to. 57 | 58 | But if you've made it here, thank you! Thanks for reading this document, and for checking this repository out in the first place. I hope your day goes really well <3 59 | 60 | ~ [Rin](https://twitter.com/lostkagamine) <3 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Rin 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rustic-c 2 | *An extraordinarily horrible idea.* 3 | 4 | ### If you want to know how this works, please [read this](HOW_ITS_MADE.md) 5 | 6 | ## What? 7 | Is Rust too memory-safe and elegant for you? 8 | Do you wish to return to something simpler? 9 | Well, have I got the solution for you, with 10 | the all new `c!` macro! Simply insert `c!` into your source 11 | and then write C like it's 1999!\* 12 | 13 | \* _Terms and conditions apply._ 14 | 15 | ## How? 16 | It's simple! Just use `c!` and write C! 17 | You can even call back to Rust! 18 | ```rs 19 | fn a_callback() { 20 | println!("hi from rust"); 21 | } 22 | 23 | fn main() { 24 | unsafe { 25 | c! { 26 | void func() { 27 | 'a_callback(); 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /c-macro/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /c-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "c-macro" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "You cannot kill me in a way that matters." 6 | tags = ["crimes"] 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | proc-macro2 = "1.0.36" 15 | quote = "1.0.15" 16 | -------------------------------------------------------------------------------- /c-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(proc_macro_span)] 2 | #![feature(extend_one)] 3 | #![feature(proc_macro_span_shrink)] 4 | #![feature(proc_macro_diagnostic)] 5 | 6 | use std::collections::HashMap; 7 | 8 | use proc_macro::{TokenStream, TokenTree, LineColumn}; 9 | use quote::quote; 10 | 11 | struct SourceCode { 12 | source: String, 13 | line: usize, 14 | col: usize, 15 | captures: Vec:: 16 | } 17 | 18 | impl SourceCode { 19 | fn new() -> Self { 20 | Self { source: "".into(), line: 1, col: 0, captures: vec![] } 21 | } 22 | 23 | // stealing stuff from mara here 24 | // THANK YOU mara!! https://twitter.com/m_ou_se 25 | // go read this, it's so good and my main inspiration behind this dumb crap 26 | // https://blog.m-ou.se/writing-python-inside-rust-1/#procedural-macros 27 | fn add_str(&mut self, s: &str) { 28 | // Let's assume for now s contains no newlines. 29 | self.source += s; 30 | self.col += s.len(); 31 | } 32 | 33 | fn add_whitespace(&mut self, loc: LineColumn) { 34 | while self.line < loc.line { 35 | self.source.push('\n'); 36 | self.line += 1; 37 | self.col = 0; 38 | } 39 | while self.col < loc.column { 40 | self.source.push(' '); 41 | self.col += 1; 42 | } 43 | } 44 | 45 | fn reconstruct_c(&mut self, strm: TokenStream) { 46 | let mut tokens = strm.clone().into_iter(); 47 | 48 | while let Some(x) = tokens.next() { 49 | match x { 50 | TokenTree::Group(y) => { 51 | let s = y.to_string(); 52 | self.add_whitespace(y.span_open().start()); 53 | self.add_str(&s[..1]); // the '[', '{' or '('. 54 | self.reconstruct_c(y.stream()); 55 | self.add_whitespace(y.span_close().start()); 56 | self.add_str(&s[s.len() - 1..]); // the ']', '}' or ')'. 57 | }, 58 | y @ _ => { 59 | if let TokenTree::Punct(v) = y { 60 | if v.as_char() == '\'' { 61 | let next_tok = tokens.next().unwrap(); 62 | if let TokenTree::Ident(id) = next_tok { 63 | self.add_whitespace(v.span().start()); 64 | let name = id.to_string(); 65 | self.add_str(&format!("((void(*)()):ONE_LBRACE::ONE_RBRACE:)")); 66 | self.captures.push(name); 67 | } else { 68 | panic!("expected identifier after '"); 69 | } 70 | } else { 71 | self.add_whitespace(v.span().start()); 72 | self.add_str(&v.to_string()); 73 | } 74 | } else { 75 | self.add_whitespace(y.span().start()); 76 | self.add_str(&y.to_string()); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | 85 | #[proc_macro] 86 | pub fn c(_strm: TokenStream) -> TokenStream { 87 | let _captures: HashMap = HashMap::new(); 88 | 89 | let mut src = SourceCode::new(); 90 | 91 | src.reconstruct_c(_strm); 92 | 93 | let src_text = src.source 94 | .replace("{", "{{") 95 | .replace("}", "}}") 96 | .replace(":ONE_LBRACE:", "{") 97 | .replace(":ONE_RBRACE:", "}"); 98 | 99 | let capts = src.captures; 100 | 101 | let mut proper_capts: Vec = vec![]; 102 | 103 | // Here we format! the pointer addresses ('rust_fn) into 104 | // a giant format-string to pass to the compile_c function 105 | // at runtime. This is an extreme code-crime and literally 106 | // has zero legitimate uses outside of doing this dumb stuff 107 | // but I'm glad it exists. Otherwise I wouldn't have known 108 | // any way to do this, lol. 109 | 110 | for i in capts { 111 | let proper_i = proc_macro2::Ident::new(&i, proc_macro2::Span::call_site()); 112 | proper_capts.push(quote!(((#proper_i as *const()) as u64))); 113 | } 114 | 115 | let tk = quote!( 116 | { 117 | let (a, b) = runtime_c::compile_c(&format!(#src_text,#(#proper_capts),*)); 118 | runtime_c::do_horrible_crimes::<()>(a, b); 119 | } 120 | ).into(); 121 | 122 | tk 123 | } -------------------------------------------------------------------------------- /runtime-c/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /runtime-c/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runtime-c" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "Does God stay in Heaven out of fear of what we have created?" 6 | tags = ["crimes"] 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | page_size = "^0.4.2" 12 | syscalls = "^0.5.0" -------------------------------------------------------------------------------- /runtime-c/src/lib.rs: -------------------------------------------------------------------------------- 1 | // - runtime-c 2 | // 3 | // "Does God stay in Heaven out of fear of what we have created?" 4 | // 5 | // A very, very, VERY horrible thing made by Rin 6 | // please never do this 7 | // 8 | // lostkagamine@outlook.com 9 | // @lostkagamine@twitter.com 10 | // https://kagamine-r.in 11 | 12 | use std::{process::Command, fs::File, io::Write}; 13 | use std::alloc::Layout; 14 | 15 | // One meg oughta be enough for everyone 16 | const ONE_MEGABYTE: usize = 1_048_576usize; 17 | 18 | extern "C" { 19 | // Yes, we need this. 20 | fn mprotect(addr: *mut (), len: usize, prot: i32); 21 | } 22 | 23 | /// Compiles C code found in `code' and returns a raw page-aligned pointer containing its data. 24 | pub unsafe fn compile_c(code: &str) -> (*mut u8, usize) { 25 | let mut temp_c_file = File::create("temp_file.c").unwrap(); 26 | temp_c_file.write_all(code.as_bytes()).unwrap(); 27 | 28 | let _command = Command::new("gcc") 29 | .args(["-Wl,--format=binary", "temp_file.c", "-o", "tmp.bin", "-c", "-m64"]) 30 | .output() 31 | .expect("gcc error"); 32 | 33 | // invoke `objcopy' to ensure we have a flat bin 34 | let _objcopy = Command::new("objcopy") 35 | .args(["-O", "binary", "-j", ".text", "tmp.bin", "flat.bin"]) 36 | .output() 37 | .expect("objcopy error"); 38 | 39 | let alignment = page_size::get(); 40 | let layout = Layout::from_size_align(ONE_MEGABYTE, alignment).expect("could not construct page-aligned 1mb layout"); 41 | 42 | let memory = std::alloc::alloc(layout); 43 | let byte_file = std::fs::read("flat.bin").expect("could not read temporary file"); 44 | let count = byte_file.len(); 45 | std::ptr::copy(byte_file.as_ptr(), memory, count); 46 | 47 | std::fs::remove_file("temp_file.c").unwrap(); 48 | std::fs::remove_file("tmp.bin").unwrap(); 49 | std::fs::remove_file("flat.bin").unwrap(); 50 | 51 | return (memory, count); 52 | } 53 | 54 | /// Does horrible, horrible things. (executes code pointed to by `ptr' where it is assumed to point to 55 | /// a memory region containing valid executable code for your current architecture. `ptr' must also 56 | /// be page-aligned or else `mprotect' will be very, very sad) 57 | pub unsafe fn do_horrible_crimes(ptr: *mut u8, size: usize) -> T { 58 | // PROT_EXEC | PROT_WRITE | PROT_READ 59 | mprotect(ptr as *mut (), size, 0x01 | 0x02 | 0x04); 60 | let x = ptr as *const (); 61 | let func = std::mem::transmute::<*const (), fn() -> T>(x); 62 | func() 63 | } 64 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" -------------------------------------------------------------------------------- /test-app/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /test-app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-app" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | runtime-c = {path = "../runtime-c"} 10 | c-macro = {path = "../c-macro"} -------------------------------------------------------------------------------- /test-app/src/main.rs: -------------------------------------------------------------------------------- 1 | use c_macro::c; 2 | 3 | fn say_hello() { 4 | println!("hello, world"); 5 | } 6 | 7 | fn main() { 8 | unsafe { 9 | c! { 10 | void x() { 11 | for (int i=0; i<5; i++) 12 | 'say_hello(); 13 | } 14 | } 15 | } 16 | } --------------------------------------------------------------------------------