Skip to content

Commit ad79508

Browse files
ivggitoleg
authored andcommitted
adds command to recipes, removes the default command hack (#1027)
* adds `command` to recipes, removes the default command hack After the introduction of the commands in bap, the recipe system wasn't updated and it was only possible to write recipes for the default `disassemble` command. We use recipes to pack our analysis into self-contained entities and we would like to be benefit from commands, e.g., we would like to be able to implement analyses that do not require a fully disassembly of the target file. This PR adds the `command' stanza to the recipe grammar. The new command stanza didn't play well with the hack that was prepending the `disassemble` command when the first argument was a file. No worries, the command is still optional, we were just able to find a much better solution, which is not a hack. Now it is the responsiblility of `Bap_main.init` to set the default command if no command was specified. So no more hacks, no need for `:` to disambiguate command and, more importantly, no need to specify the file before any other arguments on the command line, which was previously required by the hack. To summarize, if no command is specified then `disassemble` is the default command. A recipe may specify a command, it will be prepended in the right position. Users may specify a command on the command like if they want to. For example, consider a recipe (named `mc-test`) with the following specification ``` (command mc) (option llvm-x86-syntax intel) (option show-insn asm) (option show-bil) ``` The following are valid invocations of this recipe, ``` bap 48 83 ec 08 --recipe=mc-test # the command could be omitted bap mc 48 83 ec 08 --recipe=mc-test # can specify the command bap --recipe=mc-test -- 48 83 ec 08 # recipes now work fine with -- ``` * updates the testsuite to reflect the changed command syntax
1 parent d0e4fce commit ad79508

File tree

7 files changed

+133
-45
lines changed

7 files changed

+133
-45
lines changed

lib/bap_main/bap_main.ml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -598,8 +598,9 @@ module Grammar : sig
598598
?env:(string -> string option) ->
599599
?help:Format.formatter ->
600600
?default:(ctxt -> (unit,Error.t) Result.t) ->
601+
?command:string ->
601602
?err:Format.formatter ->
602-
?argv:string array -> unit -> (unit, Error.t) Result.t
603+
string array -> (unit, Error.t) Result.t
603604
end = struct
604605
open Cmdliner
605606

@@ -983,7 +984,7 @@ end = struct
983984
Term.(const serve_manpage $ served $ make_help_option plugin))
984985

985986
let eval ?(man="") ?(name=progname) ?version ?env
986-
?(help=Format.std_formatter) ?default ?err ?argv () =
987+
?(help=Format.std_formatter) ?default ?command ?err argv =
987988
let plugin_names = Plugins.list () |> List.map ~f:Plugin.name in
988989
let disabled_plugins = no_plugin_options plugin_names in
989990
let plugin_options = concat_plugins () in
@@ -1003,7 +1004,16 @@ end = struct
10031004
| Some f -> Term.(const (fun p -> match p with
10041005
| Error _ as err -> err
10051006
| Ok () -> f @@ Context.request ()) $ plugins) in
1006-
match Term.eval_choice ~catch:false ?env ~help ?err ?argv
1007+
let argv = match command with
1008+
| None -> argv
1009+
| Some cmd -> match Array.to_list argv with
1010+
| prog :: arg :: rest ->
1011+
if List.exists commands ~f:(fun (_,info) ->
1012+
String.is_prefix ~prefix:arg (Term.name info))
1013+
then argv
1014+
else Array.of_list (prog::cmd::arg::rest)
1015+
| [_] | [] -> argv in
1016+
match Term.eval_choice ~catch:false ?env ~help ?err ~argv
10071017
(main,main_info) commands with
10081018
| `Ok (Ok ()) -> Ok ()
10091019
| `Ok (Error _ as err) -> err
@@ -1124,6 +1134,7 @@ let init
11241134
?env ?log ?out ?err ?man
11251135
?name ?(version=Bap_main_config.version)
11261136
?default
1137+
?default_command
11271138
() =
11281139
match state.contents with
11291140
| Loaded _ -> Error Error.Already_initialized
@@ -1134,7 +1145,7 @@ let init
11341145
| None | Some None -> Ok argv
11351146
| Some (Some spec) ->
11361147
load_recipe spec >>= fun spec ->
1137-
Ok (Array.append argv @@ Bap_recipe.argv spec) in
1148+
Ok (Bap_recipe.argv ~argv spec) in
11381149
argv >>= fun argv ->
11391150
let log = match log with
11401151
| Some _ -> log
@@ -1152,9 +1163,9 @@ let init
11521163
| Ok p -> `Fst p
11531164
| Error (p,e) -> `Snd (p,e)) in
11541165
if List.is_empty failures
1155-
then match
1156-
Grammar.eval ?name ~version ?env ?help:out
1157-
?err ?man ~ argv ?default ()
1166+
then match Grammar.eval argv
1167+
?name ~version ?env ?help:out
1168+
?err ?man ?default ?command:default_command
11581169
with
11591170
| Ok () ->
11601171
state := Loaded plugins;

lib/bap_main/bap_main.mli

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,11 @@ type ctxt
438438
@parameter version defaults to the BAP Framework version.
439439
440440
@parameter default, if specified, then this function will be invoked
441-
when no command was specified in the command line.
441+
when no command line arguments were provided.
442+
443+
@parameter default_command, if specified, then this command will
444+
be used when command line arguments are provided but do not
445+
specify a command. @since 2.1.0.
442446
*)
443447
val init :
444448
?features:string list ->
@@ -453,6 +457,7 @@ val init :
453457
?name:string ->
454458
?version:string ->
455459
?default:(ctxt -> (unit,error) result) ->
460+
?default_command:string ->
456461
unit -> (unit, error) result
457462

458463

lib/bap_recipe/bap_recipe.ml

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ type param = {
3333
}
3434

3535
type item =
36+
| Cmd of string
3637
| Use of string
3738
| Opt of string * string list
3839
| Par of param
3940

4041
type spec = {
42+
cmd : string option;
4143
uses : string list;
4244
opts : (string * string list) list;
4345
pars : param list;
@@ -65,6 +67,7 @@ type t = {
6567
}
6668

6769
let empty_spec = {
70+
cmd = None;
6871
uses = [];
6972
opts = [];
7073
pars = [];
@@ -89,6 +92,7 @@ let item_of_sexp = function
8992
atoms args >>| fun args -> Opt (name, args)
9093
| Sexp.List (Sexp.Atom "parameter" :: Sexp.Atom s :: _) ->
9194
Error (Bad_param s)
95+
| Sexp.List [Sexp.Atom "command"; Sexp.Atom name] -> Ok (Cmd name)
9296
| x -> Error (Bad_item x)
9397

9498

@@ -97,7 +101,8 @@ let items_of_sexps xs = Result.all (List.map xs ~f:item_of_sexp)
97101
let spec_of_items = List.fold ~init:empty_spec ~f:(fun spec -> function
98102
| Use x -> {spec with uses = x :: spec.uses}
99103
| Opt (x,xs) -> {spec with opts = (x,xs) :: spec.opts}
100-
| Par x -> {spec with pars = x :: spec.pars})
104+
| Par x -> {spec with pars = x :: spec.pars}
105+
| Cmd name -> {spec with cmd = Some name})
101106

102107
let (/) = Filename.concat
103108

@@ -264,7 +269,28 @@ let load ?paths ?env name =
264269
let rec args t =
265270
List.concat_map t.loads ~f:args @ t.args
266271

267-
let argv t = Array.of_list @@ args t
272+
let prepend_before_dash_dash argv args =
273+
match Array.findi argv ~f:(fun _ -> String.equal "--") with
274+
| None -> Array.append argv args
275+
| Some (p,_) -> Array.concat [
276+
Array.subo ~len:p argv;
277+
args;
278+
Array.subo ~pos:p argv
279+
]
280+
281+
let args t = Array.of_list @@ args t
282+
let command t = t.spec.cmd
283+
let argv ?(argv=[||]) t = match command t with
284+
| None -> prepend_before_dash_dash argv (args t)
285+
| Some cmd ->
286+
let argv = Array.of_list @@ match Array.to_list argv with
287+
| [] | [_] as argv -> argv @ [cmd]
288+
| self :: arg :: rest when String.is_prefix ~prefix:arg cmd ->
289+
self :: cmd :: rest
290+
| self :: arg :: rest ->
291+
self :: cmd :: arg :: rest in
292+
prepend_before_dash_dash argv (args t)
293+
268294

269295
let rec params t = t.spec.pars @ List.concat_map t.loads ~f:params
270296

lib/bap_recipe/bap_recipe.mli

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,25 @@ type error
33
type t
44
type param
55

6+
7+
8+
(** [load recipe] searches and loads a recipe.
9+
10+
Searches a recipe in the search paths specified with the [paths]
11+
parameter, see the {!search} function for the description of the
12+
search rules.
13+
14+
If a recipe is found then it is loaded. All recipes included in
15+
the found recipe are also loaded with the [load] function using
16+
the same [paths] and [env] arguments.
17+
18+
During the loading recipe parameters are substituted using the
19+
[env] mapping.
20+
21+
If a recipe includes files, they are unpacked into a separate
22+
folder, which will be removed when {!close} is called on the
23+
recipe.
24+
*)
625
val load :
726
?paths:string list ->
827
?env:(string * string) list ->
@@ -17,18 +36,57 @@ val load :
1736
*)
1837
val search : string list -> string list
1938

20-
(** [argv recipe] returns the argument vector for recipe.*)
21-
val argv : t -> string array
39+
40+
(** [args recipe] is an array of arguments specified in the recipe. *)
41+
val args : t -> string array
42+
43+
44+
(** [command recipe] returns the [recipe] command, if one exists. *)
45+
val command : t -> string option
46+
47+
(** [argv recipe] builds an argument vector from the [recipe].
48+
49+
All arguments are appended to the passed [argv] (which defaults to
50+
an empty array, not [Sys.argv]). If [argv] contains [--] then
51+
arguments are prepended before [--] with everything after [--]
52+
left intact.
53+
54+
If the [recipe] specifies a command then it is inserted after the
55+
first argument in [argv] (if such exists), unless the second
56+
argument already specifies the same command, in which case it is
57+
ignored.
58+
*)
59+
val argv : ?argv:string array -> t -> string array
60+
61+
62+
(** [close recipe] closes the recipe and clears all associated resources. *)
2263
val close : t -> unit
64+
65+
66+
(** [doc recipe] is the recipe description. *)
2367
val doc : t -> string
68+
69+
70+
(** [params recipe] is the list of recipe parameters. *)
2471
val params : t -> param list
25-
val pp_error : Format.formatter -> error -> unit
2672

2773

74+
(** [pp_error ppf err] prints the error message [err]. *)
75+
val pp_error : Format.formatter -> error -> unit
76+
77+
(** Recipe Parameters. *)
2878
module Param : sig
2979
type t = param
80+
81+
(** [name p] the parameter [p] name. *)
3082
val name : param -> string
83+
84+
(** [doc p] the parameter [p] description. *)
3185
val doc : param -> string
86+
87+
(** [default p] the default value of the parameter [p]. *)
3288
val default : param -> string
89+
90+
(** [pp ppf p] prints the human-readable description of [p]. *)
3391
val pp : Format.formatter -> param -> unit
3492
end

plugins/recipe_command/recipe_command_main.ml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ in parameter called $(b,prefix), that is substituted with the path
5353
to the recipe top folder. See the $(b,parameter) command to learn
5454
how to introduce parameters.
5555

56-
The parameter command introduces a parameter to the recipe, i.e.,
56+
The $(b,parameter) command introduces a parameter to the recipe, i.e.,
5757
a variable ingredient that could be changed when the recipe is
5858
used. The parameter command has 3 arguments, all required. The
5959
first argument is the parameter name, the second is the default
@@ -71,6 +71,11 @@ If the parameter is not set through the command line, then it will
7171
be substituted with $(b,128) otherwise it will receive whatever
7272
value a user has passed.
7373
74+
The $(b,command) stanza specifies the command that the recipe should
75+
run. It is optional, since recipes could be generic and applicable to
76+
different commands, which gives an extra freedom to the recipe
77+
user.
78+
7479
Finally, the $(b,extend) command is like the include statement in
7580
the C preprocessor as it includes all the ingredients from another
7681
recipe. (Make sure that you're not introducing loops!). The
@@ -81,10 +86,11 @@ include.;
8186
8287
```
8388
recipe ::= {<recipe-item>}
84-
recipe-item ::= <option> | <parameter> | <extend>
89+
recipe-item ::= <option> | <parameter> | <extend> | <command>
8590
option ::= (option <atom> {<atom>})
8691
parameter ::= (parameter <atom> <atom> <atom>)
8792
extend ::= (extend <atom>)
93+
command ::= (command <atom>)
8894
```
8995
|}
9096

src/bap_frontend.ml

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,6 @@ open Bap_main.Extension
3030

3131
module type unit = sig end
3232

33-
let qualified cmd =
34-
if String.length cmd > 2 && Char.equal cmd.[0] ':'
35-
then Some (String.subo ~pos:1 cmd)
36-
else None
37-
38-
let is_option = String.is_prefix ~prefix:"-"
39-
40-
(* To preserve backward compatibility and enhance usability we
41-
automatically add the `disassemble' command if the first argument is
42-
a file. However, since we can have a potential clash between a
43-
command name, a command name could be prefixed with `:`, so that
44-
it will be interpreter literally as a keyword, not as a file.
45-
*)
46-
let autocorrect_input args =
47-
Array.of_list @@ match Array.to_list args with
48-
| [] | [_] as args -> args
49-
| name :: arg :: args as input ->
50-
if is_option arg then input else match qualified arg with
51-
| Some cmd -> name::cmd::args
52-
| None ->
53-
if Sys.file_exists arg && not (Sys.is_directory arg)
54-
then name :: "disassemble" :: arg :: args
55-
else name :: arg :: args
5633

5734
let pp_info ppf infos =
5835
List.iter infos ~f:(fun info ->
@@ -81,10 +58,13 @@ Commands:
8158
Plugins:
8259
%a
8360

84-
If no command is specified and the first parameter is a file,
85-
then the `disassemble' command is assumed. If the name of a
86-
command conflicts with an existing file, prefix the name with
87-
`:', e.g., `bap :plugins'.
61+
If no command line options or arguments are specified, then this
62+
message is printed. To hush this message specify the `.' command,
63+
e.g., `bap .'. The command could be omitted altogether, in that case the
64+
`disassemble' command is assumed, e.g., `bap /bin/ls' is the same
65+
as `bap disassemble /bin/ls'. Not that the default command is
66+
subject to change, so it is better not to rely on this behavior in
67+
your automation tools.
8868

8969
Run 'bap <COMMAND> --help' for more information a command.
9070
Run 'bap --<PLUGIN>-help for more information about a plugin.
@@ -296,14 +276,16 @@ let () =
296276

297277
let () =
298278
let _unused : (module unit) = (module Bap.Std) in
299-
let argv = autocorrect_input Sys.argv in
300279
let () =
301280
try if Sys.getenv "BAP_DEBUG" <> "0" then
302281
Printexc.record_backtrace true
303282
with Caml.Not_found -> () in
304283
Sys.(set_signal sigint (Signal_handle exit));
305284
at_exit Format.(pp_print_flush err_formatter);
306-
match Bap_main.init ~default:print_info ~name:"bap" ~man ~argv () with
285+
match Bap_main.init ~default:print_info
286+
~default_command:"disassemble"
287+
~name:"bap" ~man ~argv:Sys.argv ()
288+
with
307289
| Ok () -> ()
308290
| Error (Error.Exit_requested code) -> exit code
309291
| Error Error.Configuration -> exit 1

0 commit comments

Comments
 (0)