Current section
Files
Jump to
Current section
Files
lib/c_translator.ex
import GVA
defmodule Honey.TranslatedCode do
defstruct [:code, :return_var_name, :return_var_type]
def new(code \\ "", return_var_name \\ "0var_name_err") do
%Honey.TranslatedCode{code: code, return_var_name: return_var_name}
end
end
defmodule Honey.Translator do
def get_c_var_name(var_ast) do
{var_name, meta, var_context} = var_ast
Atom.to_string(var_name) <>
inspect_no_limit(meta[:version]) <>
Atom.to_string(var_context)
end
def get_new_helper_var_name() do
counter = gget(:global_var, :helper_var_counter)
gput(:global_var, :helper_var_counter, counter + 1)
"helper_var_#{counter}"
end
defp inspect_no_limit(value) do
Kernel.inspect(value, limit: :infinity, printable_limit: :infinity)
end
def to_c(tree, context \\ {})
# Variables
def to_c({var, var_meta, var_context}, _context) when is_atom(var) and is_atom(var_context) do
c_var_name = get_c_var_name({var, var_meta, var_context})
Honey.TranslatedCode.new("", c_var_name)
end
# Blocks
def to_c({:__block__, _, [expr]}, context) do
to_c(expr, context)
end
def to_c({:__block__, _, _} = ast, context) do
block = block_to_c(ast, context)
%Honey.TranslatedCode{block | code: "\n" <> block.code <> "\n"}
end
# Erlang functions
def to_c({{:., _, [:erlang, function]}, _, [lhs, rhs]}, _context) do
func_string =
case function do
:+ ->
"Sum"
:- ->
"Subtract"
:* ->
"Multiply"
:/ ->
"Divide"
:== ->
"Equals"
# :> ->
# " ..."
# :>= ->
# " ... "
# :< ->
# " ..."
# :<= ->
# " ... "
# :bsr ->
# " ... "
# :bsl ->
# " ... "
_ ->
raise "Erlang function not supported: #{Atom.to_string(function)}"
end
lhs_in_c = to_c(lhs)
rhs_in_c = to_c(rhs)
c_var_name = get_new_helper_var_name()
code =
lhs_in_c.code <>
"\n" <>
rhs_in_c.code <>
"\n" <>
"BINARY_OPERATION(#{c_var_name}, #{func_string}, #{lhs_in_c.return_var_name}, #{rhs_in_c.return_var_name})" <>
"\n"
Honey.TranslatedCode.new(code, c_var_name)
end
# C libraries
def to_c({{:., _, [Honey.Bpf.Bpf_helpers, function]}, _, params}, context) do
case function do
:bpf_printk ->
[[string | other_params]] = params
if(!is_bitstring(string)) do
throw(
"First argument of bpf_printk must be a string. Received: #{Macro.to_string(params)}"
)
end
string = String.replace(string, "\n", "\\n")
code_vars =
Enum.map(other_params, fn expr ->
to_c(expr, context)
end)
code =
Enum.reduce(code_vars, "", fn translated, so_far ->
so_far <> translated.code
end)
vars =
Enum.reduce(code_vars, "", fn translated, so_far ->
so_far = if so_far != "", do: so_far <> ", ", else: ""
so_far <> translated.return_var_name <> ".value.integer"
end)
result_var = get_new_helper_var_name()
code =
code <>
"bpf_printk(\"#{string}\", #{vars});\n" <>
"Generic #{result_var} = {.type = INTEGER, .value.integer = 0};"
# TODO: Return nil insted of 0, as Elixir would
Honey.TranslatedCode.new(code, result_var)
# TODO: Maps stopped working after the addition of dynamic types.
:bpf_map_lookup_elem ->
[map, key_ast] = params
if(!is_atom(map)) do
throw "bpf_map_lookup_elem: 'map' must be an atom. Received: #{Macro.to_string(map)}"
end
str_map_name = Atom.to_string(map)
key = to_c(key_ast, context)
result_var_pointer = get_new_helper_var_name()
result_var = get_new_helper_var_name()
code = key.code <>
"if(#{key.return_var_name}.type != INTEGER) {
op_result = (OpResult){.exception = 1, .exception_msg = \"(MapKey) Keys passed to bpf_map_lookup_elem is not integer.\"};
goto CATCH;
}
Generic *#{result_var_pointer} = bpf_map_lookup_elem(&#{str_map_name}, &(#{key.return_var_name}.value.integer));
if(!#{result_var_pointer}) {
op_result = (OpResult){.exception = 1, .exception_msg = \"(MapAcess) Impossible to access map '#{str_map_name}' with the key informed.\"};
goto CATCH;
}
Generic #{result_var} = *#{result_var_pointer};
"
Honey.TranslatedCode.new(code, result_var)
:bpf_map_update_elem ->
[map, key_ast, value_ast] = params
if(!is_atom(map)) do
throw "bpf_map_update_elem: 'map' must be an atom. Received: #{Macro.to_string(map)}"
end
str_map_name = Atom.to_string(map)
# if(!is_atom(flags)) do
# throw "bpf_map_update_elem: 'flags' must be an atom. Received: #{Macro.to_string(map)}"
# end
# flags_map_name = Atom.to_string(flags)
# |> String.replace("Elixir.", "")
key = to_c(key_ast, context)
value = to_c(value_ast, context)
result_var_c = get_new_helper_var_name()
result_var = get_new_helper_var_name()
code = key.code <>
value.code <>
"if(#{key.return_var_name}.type != INTEGER) {
op_result = (OpResult){.exception = 1, .exception_msg = \"(MapKey) Keys passed to bpf_map_update_elem is not integer.\"};
goto CATCH;
}
int #{result_var_c} = bpf_map_update_elem(&#{str_map_name}, &(#{key.return_var_name}.value.integer), &#{value.return_var_name}, BPF_ANY);
Generic #{result_var} = (Generic){.type = INTEGER, .value.integer = #{result_var_c}};
"
Honey.TranslatedCode.new(code, result_var)
:bpf_get_current_pid_tgid ->
result_var = get_new_helper_var_name()
code =
"Generic #{result_var} = {.type = INTEGER, .value.integer = bpf_get_current_pid_tgid()};\n"
Honey.TranslatedCode.new(code, result_var)
end
end
# General dot operator
def to_c({{:., _, [var, property]}, _, _}, _context) do
var_name_in_c = get_c_var_name(var)
property_var = get_new_helper_var_name()
str_name_var = get_new_helper_var_name()
code =
"Generic #{property_var} = {0};" <>
"char #{str_name_var}[20] = \"#{Atom.to_string(property)}\";" <>
"getMember(&op_result, &#{var_name_in_c}, #{str_name_var}, &#{property_var});" <>
"if (op_result.exception) goto CATCH;"
Honey.TranslatedCode.new(code, property_var)
end
# function raise/1
def to_c({:raise, _meta, [msg]}, _context) when is_bitstring(msg) do
new_var_name = get_new_helper_var_name()
code = "Generic #{new_var_name} = (Generic){0};
op_result = (OpResult){ .exception = 1, .exception_msg = \"(RaiseException) #{msg}\"};
goto CATCH;\n"
Honey.TranslatedCode.new(code, new_var_name)
end
# Match operator, not complete
def to_c({:=, _, [lhs, rhs]}, _context) do
rhs_in_c = to_c(rhs)
c_var_name = get_c_var_name(lhs)
code =
rhs_in_c.code <>
"Generic #{c_var_name} = #{rhs_in_c.return_var_name};\n"
Honey.TranslatedCode.new(code, c_var_name)
end
# Cond
def to_c({:cond, _, [[do: conds]]}, _context) do
cond_var_name_in_c = get_new_helper_var_name()
cond_code = cond_statments_to_c(conds, cond_var_name_in_c)
code = "Generic #{cond_var_name_in_c} = {.type = INTEGER, .value.integer = 0};\n" <> cond_code
Honey.TranslatedCode.new(code, cond_var_name_in_c)
end
# Other structures
def to_c(other, _context) do
{is_cons, code} = is_constant(other)
cond do
is_cons ->
code
true ->
IO.puts("We cannot convert this structure yet:")
IO.inspect(other)
raise "We cannot convert this structure yet."
end
end
def is_constant(item) do
var_name_in_c = get_new_helper_var_name()
cond do
is_integer(item) ->
{true,
Honey.TranslatedCode.new(
"Generic #{var_name_in_c} = {.type = INTEGER, .value.integer = #{item}};",
var_name_in_c
)}
is_number(item) ->
{true,
Honey.TranslatedCode.new(
"Generic #{var_name_in_c} = {.type = DOUBLE, .value.double_precision = #{item}};",
var_name_in_c
)}
# Considering only strings for now
is_bitstring(item) ->
# TODO: Check whether the zero-termination is ok the way it is
# TODO: consider other special chars
str = String.replace(item, "\n", "\\n")
str_len = String.length(str) + 1
new_var_name = get_new_helper_var_name()
end_var_name = "end_" <> new_var_name
len_var_name = "len_" <> new_var_name
code = "
unsigned #{len_var_name} = #{str_len};
unsigned #{end_var_name} = *string_pool_index + #{len_var_name} - 1;
if(#{end_var_name} + 1 >= STRING_POOL_SIZE) {
op_result = (OpResult){.exception = 1, .exception_msg = \"(MemoryLimitReached) Impossible to create string, the string pool is full.\"};
goto CATCH;
}
if(*string_pool_index < STRING_POOL_SIZE - #{len_var_name}) {
__builtin_memcpy(&(*string_pool)[*string_pool_index], \"#{str}\", #{len_var_name});
}
Generic #{var_name_in_c} = {.type = STRING, .value.string = (String){.start = *string_pool_index, .end = #{end_var_name}}};
*string_pool_index = #{end_var_name} + 1;
"
{true, Honey.TranslatedCode.new(code, var_name_in_c)}
is_atom(item) ->
# TODO: Convert arbitrary atoms
value =
case item do
true ->
"ATOM_TRUE"
false ->
"ATOM_FALSE"
nil ->
"ATOM_NIL"
_ ->
raise "We cannot convert arbitrary atoms yet (only 'true', 'false' and 'nil')."
end
code = "Generic #{var_name_in_c} = #{value};"
{true, Honey.TranslatedCode.new(code, var_name_in_c)}
is_binary(item) ->
raise "We cannot convert binary yet."
# TODO: create an option for tuples and arrays
true ->
{false, nil}
end
end
def cond_statments_to_c([], cond_var_name_in_c) do
"#{cond_var_name_in_c} = (Generic){.type = ATOM, .value.string = (String){0, 2}};"
end
def cond_statments_to_c([cond_stat | other_conds], cond_var_name_in_c) do
{:->, _, [[condition] | [block]]} = cond_stat
condition_in_c = to_c(condition)
block_in_c = to_c(block)
condition_in_c.code <>
"\n" <>
"if (to_bool(&#{condition_in_c.return_var_name})) {\n" <>
block_in_c.code <>
"\n" <>
"#{cond_var_name_in_c} = #{block_in_c.return_var_name};\n}\n" <>
"else {\n" <>
cond_statments_to_c(other_conds, cond_var_name_in_c) <> "\n}\n"
end
defp block_to_c({:__block__, _, exprs}, context) do
Enum.reduce(exprs, Honey.TranslatedCode.new(), fn expr, translated_so_far ->
translated_expr = to_c(expr, context)
%Honey.TranslatedCode{
translated_expr
| code: translated_so_far.code <> "\n" <> translated_expr.code
}
end)
end
defp ensure_right_type(type) do
cond do
type in ["", nil] ->
raise "The main/1 function must be preceded by a @sec indicating the type of the program."
type not in ["tracepoint/syscalls/sys_enter_kill"] ->
raise "We cannot convert this Program Type yet: #{type}"
true ->
true
end
end
def translate(func_name, ast, sec, license, requires, elixir_maps) do
if(func_name == "main") do
# TODO: replace this global counter with something more idiomatic in elixir
gnew(:global_var)
gput(:global_var, :helper_var_counter, 0)
ensure_right_type(sec)
translated_code = to_c(ast)
Honey.Boilerplates.config(sec, ["ctx0nil"], license, elixir_maps, requires, translated_code)
|> Honey.Boilerplates.get_whole_code()
else
false
end
end
end