Add sctrace tool
This commit is contained in:
parent
f1d90b8c69
commit
f3fb82c6f1
|
|
@ -66,6 +66,15 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "align_ext"
|
||||
version = "0.1.0"
|
||||
|
|
@ -76,6 +85,56 @@ version = "0.2.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aster-bigtcp"
|
||||
version = "0.1.0"
|
||||
|
|
@ -483,12 +542,58 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "cobs"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "component"
|
||||
version = "0.1.0"
|
||||
|
|
@ -600,7 +705,7 @@ dependencies = [
|
|||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"strsim 0.10.0",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
|
|
@ -886,6 +991,12 @@ dependencies = [
|
|||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "iced-x86"
|
||||
version = "1.21.0"
|
||||
|
|
@ -963,6 +1074,12 @@ dependencies = [
|
|||
"ghost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
|
|
@ -1171,6 +1288,12 @@ dependencies = [
|
|||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "multiboot2"
|
||||
version = "0.24.0"
|
||||
|
|
@ -1201,6 +1324,27 @@ version = "6.6.666"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf5a574dadd7941adeaa71823ecba5e28331b8313fb2e1c6a5c7e5981ea53ad6"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nougat"
|
||||
version = "0.2.4"
|
||||
|
|
@ -1243,6 +1387,12 @@ version = "1.21.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
|
|
@ -1541,6 +1691,35 @@ dependencies = [
|
|||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "riscv"
|
||||
version = "0.15.0"
|
||||
|
|
@ -1622,6 +1801,16 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sctrace"
|
||||
version = "0.16.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"nix",
|
||||
"nom",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
|
|
@ -1704,6 +1893,12 @@ version = "0.10.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
|
|
@ -1940,6 +2135,12 @@ dependencies = [
|
|||
"gimli 0.31.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
|
|
@ -1992,16 +2193,31 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets 0.53.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2010,14 +2226,31 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm 0.52.6",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
"windows_i686_gnullvm 0.53.1",
|
||||
"windows_i686_msvc 0.53.1",
|
||||
"windows_x86_64_gnu 0.53.1",
|
||||
"windows_x86_64_gnullvm 0.53.1",
|
||||
"windows_x86_64_msvc 0.53.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2026,48 +2259,96 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.5.40"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ members = [
|
|||
"kernel/libs/typeflags-util",
|
||||
"kernel/libs/atomic-integer-wrapper",
|
||||
"kernel/libs/xarray",
|
||||
"tools/sctrace",
|
||||
]
|
||||
exclude = [
|
||||
"kernel/libs/comp-sys/cargo-component",
|
||||
|
|
@ -48,6 +49,7 @@ exclude = [
|
|||
"kernel/libs/comp-sys/component-macro",
|
||||
"kernel/libs/comp-sys/controlled",
|
||||
"osdk",
|
||||
"tools/sctrace",
|
||||
]
|
||||
|
||||
[workspace.lints.rust]
|
||||
|
|
|
|||
3
Makefile
3
Makefile
|
|
@ -189,7 +189,8 @@ NON_OSDK_CRATES := \
|
|||
kernel/libs/keyable-arc \
|
||||
kernel/libs/logo-ascii-art \
|
||||
kernel/libs/typeflags \
|
||||
kernel/libs/typeflags-util
|
||||
kernel/libs/typeflags-util \
|
||||
tools/sctrace
|
||||
|
||||
# In contrast, OSDK crates depend on OSTD (or being `ostd` itself)
|
||||
# and need to be built or tested with OSDK.
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
|
|
@ -0,0 +1,343 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "sctrace"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"nix",
|
||||
"nom",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "sctrace"
|
||||
version = "0.16.1"
|
||||
edition = "2024"
|
||||
description = "Syscall Compatibility Tracer (sctrace) that analyzes and validates system call against System Call Matching Language (SCML)"
|
||||
license = "MPL-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/asterinas/asterinas"
|
||||
|
||||
[lib]
|
||||
name = "sctrace"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.47", features = ["derive"] }
|
||||
regex = "1.11.2"
|
||||
nom = "7"
|
||||
nix = { version = "0.27", features = ["process"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
# Syscall Compatibility Tracer
|
||||
|
||||
Syscall Compatibility Tracer (`sctrace`) is a powerful system call compatibility
|
||||
verification tool that analyzes and validates system call against user-defined
|
||||
patterns. Written in
|
||||
[SCML (System Call Matching Language)](https://asterinas.github.io/book/kernel/linux-compatibility/syscall-feature-coverage/system-call-matching-language.html),
|
||||
these patterns describe supported functionality of system calls.
|
||||
`sctrace` supports both real-time monitoring of running programs and post-analysis of
|
||||
existing trace logs, providing comprehensive insights into system call compatibility
|
||||
with intuitive pattern matching and visual feedback.
|
||||
|
||||
## Features
|
||||
|
||||
- **Pattern-based filtering**: Define system call patterns using SCML syntax
|
||||
- **Dual mode operation**:
|
||||
- Online mode: Real-time tracing of running programs
|
||||
- Offline mode: Analysis of existing strace log files
|
||||
- **Multi-threaded support**: Automatic handling of multi-threaded program traces with syscall reconstruction.
|
||||
When tracing multi-threaded programs, strace may split system calls across multiple lines due to thread interleaving.
|
||||
`sctrace` automatically handles this reconstruction.
|
||||
- **Multiple SCML files support**: Specify multiple `.scml` files as arguments to load all of them.
|
||||
Each file maintains its own scope for bitflags and struct definitions, preventing cross-file pollution.
|
||||
|
||||
## How to build and install
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [**strace**](https://strace.io/) version 5.15 or higher (for online mode)
|
||||
- Debian/Ubuntu: `sudo apt install strace`
|
||||
- Fedora/RHEL: `sudo dnf install strace`
|
||||
- Rust toolchain
|
||||
|
||||
### Build instructions
|
||||
|
||||
Make sure you have Rust installed, then build the project:
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
The binary will be available at `target/release/sctrace`.
|
||||
|
||||
### Installation instructions
|
||||
|
||||
To install the binary (for example, to `/usr/local/bin`),
|
||||
you can use:
|
||||
|
||||
```bash
|
||||
sudo cp target/release/sctrace /usr/local/bin/
|
||||
```
|
||||
|
||||
Or you can install from `crates.io` directly (Recommended):
|
||||
|
||||
```bash
|
||||
cargo install sctrace
|
||||
```
|
||||
|
||||
This will automatically download, build, and install the latest version of `sctrace`.
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Syntax
|
||||
|
||||
```bash
|
||||
sctrace <SCML_FILE1> [SCML_FILE2 ...] [OPTIONS] -- [program] [args...]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `--input <FILE>`: Specify input file for offline mode
|
||||
- `--quiet`: Enable quiet mode (only show unsupported calls)
|
||||
|
||||
### Online Mode (Real-time tracing)
|
||||
|
||||
Trace a program in real-time:
|
||||
|
||||
```bash
|
||||
sctrace pattern1.scml pattern2.scml -- ls -la
|
||||
sctrace file_ops.scml network.scml --quiet -- ./my_program arg1 arg2
|
||||
```
|
||||
|
||||
### Offline Mode (Log file analysis)
|
||||
|
||||
Analyze an existing strace log file:
|
||||
|
||||
```bash
|
||||
sctrace pattern1.scml pattern2.scml --input trace.log
|
||||
sctrace file_ops.scml network.scml --input trace.log --quiet
|
||||
```
|
||||
|
||||
**Note**: When generating strace logs for offline analysis, use `-yy` and `-f` flags:
|
||||
|
||||
```bash
|
||||
strace -yy -f -o trace.log ls -la
|
||||
```
|
||||
|
||||
- `-yy`: Print paths associated with file descriptor arguments
|
||||
- `-f`: Trace child processes created by fork/vfork/clone
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Basic File Operations
|
||||
|
||||
Create `file_ops.scml`:
|
||||
```scml
|
||||
openat(dirfd, flags = O_RDONLY | O_WRONLY | O_RDWR, mode);
|
||||
read(fd, buf, count = <INTEGER>);
|
||||
write(fd, buf, count = <INTEGER>);
|
||||
close(fd);
|
||||
```
|
||||
|
||||
Run:
|
||||
```bash
|
||||
sctrace file_ops.scml -- cat /etc/passwd
|
||||
```
|
||||
|
||||
### Example 2: Network Operations
|
||||
|
||||
Create `network.scml`:
|
||||
```scml
|
||||
socket(domain = AF_INET | AF_INET6, type = SOCK_STREAM | SOCK_DGRAM, protocol);
|
||||
connect(sockfd, addr, addrlen);
|
||||
send(sockfd, buf, len, flags);
|
||||
recv(sockfd, buf, len, flags);
|
||||
```
|
||||
|
||||
Run:
|
||||
```bash
|
||||
sctrace network.scml -- curl http://example.com
|
||||
```
|
||||
|
||||
### Example 3: Using Asterinas Compatibility Patterns
|
||||
|
||||
Use the provided directory [syscall-feature-coverage](../../book/src/kernel/linux-compatibility/syscall-feature-coverage) (work in progress) and
|
||||
test with various commands:
|
||||
|
||||
```bash
|
||||
# Monitor file system operations
|
||||
sctrace $(find . -name "*.scml") -- tree .
|
||||
|
||||
# Monitor process information calls
|
||||
sctrace $(find . -name "*.scml") -- top
|
||||
|
||||
# Monitor network operations
|
||||
sctrace $(find . -name "*.scml") -- ping 127.0.0.1
|
||||
```
|
||||
|
||||
### Example 4: Offline Analysis
|
||||
|
||||
```bash
|
||||
# Generate trace log
|
||||
strace -yy -f -o trace.log ls -la
|
||||
|
||||
# Analyze with sctrace
|
||||
sctrace patterns.scml --input trace.log
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
`sctrace` provides colored output to distinguish between supported and unsupported system calls:
|
||||
|
||||
- **Supported calls**: Normal output (or hidden in quiet mode)
|
||||
- **Unsupported calls**: Highlighted in red with "unsupported" message
|
||||
|
||||
### Example Output
|
||||
|
||||
```
|
||||
openat(AT_FDCWD, "/etc/passwd", O_RDONLY) = 3
|
||||
read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 1234
|
||||
close(3) = 0
|
||||
chmod("/tmp/test", 0755) (unsupported)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `clap`: Command-line argument parsing
|
||||
- `regex`: Regular expression support
|
||||
- `nom`: Parser combinator library
|
||||
- `nix`: Unix system interface for process management
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Permission Issues
|
||||
|
||||
For online tracing, you may need elevated privileges:
|
||||
|
||||
```bash
|
||||
sudo sctrace patterns.scml -- target_program
|
||||
```
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! # Sctrace - Syscall Compatibility Tracer
|
||||
//!
|
||||
//! `sctrace` is a library for analyzing `strace` logs against SCML (Syscall Matching Language)
|
||||
//! patterns to determine syscall compatibility. It provides APIs for parsing `strace` output,
|
||||
//! matching syscalls against pattern specifications, and reporting compatibility results.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use sctrace::{CliReporterBuilder, Patterns, SctraceBuilder, StraceLogStream};
|
||||
//!
|
||||
//! fn main() -> Result<(), String> {
|
||||
//! let scml_files = vec!["patterns1.scml", "patterns2.scml"];
|
||||
//!
|
||||
//! let sctrace = SctraceBuilder::new()
|
||||
//! .strace(StraceLogStream::open_file("strace.log")?)
|
||||
//! .patterns(Patterns::from_scml_files(&scml_files)?)
|
||||
//! .reporter(CliReporterBuilder::new().quiet().build())
|
||||
//! .build();
|
||||
//!
|
||||
//! let _ = sctrace.run();
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
mod scml_matcher;
|
||||
mod scml_parser;
|
||||
mod strace_parser;
|
||||
mod trace;
|
||||
|
||||
use std::io::Lines;
|
||||
|
||||
use scml_matcher::Matcher;
|
||||
pub use scml_parser::Patterns;
|
||||
use strace_parser::{StraceParseError, Syscall};
|
||||
pub use trace::StraceLogStream;
|
||||
|
||||
/// Builder for creating an `Sctrace` instance.
|
||||
pub struct SctraceBuilder<'a> {
|
||||
log_stream: Option<StraceLogStream>,
|
||||
patterns: Option<Patterns<'a>>,
|
||||
reporter: Option<CliReporter>,
|
||||
}
|
||||
|
||||
impl<'a> SctraceBuilder<'a> {
|
||||
/// Creates a new `SctraceBuilder` instance.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
log_stream: None,
|
||||
patterns: None,
|
||||
reporter: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the strace log stream for the tracer.
|
||||
pub fn strace(mut self, stream: StraceLogStream) -> Self {
|
||||
self.log_stream = Some(stream);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the SCML patterns for the tracer.
|
||||
pub fn patterns(mut self, patterns: Patterns<'a>) -> Self {
|
||||
self.patterns = Some(patterns);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the reporter for the tracer.
|
||||
pub fn reporter(mut self, reporter: CliReporter) -> Self {
|
||||
self.reporter = Some(reporter);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the `Sctrace` instance with the specified components.
|
||||
pub fn build(self) -> Sctrace<'a> {
|
||||
Sctrace {
|
||||
strace_iter: self.log_stream.expect("`log_stream` is required").lines(),
|
||||
patterns: self.patterns.expect("`patterns` is required"),
|
||||
reporter: self.reporter.expect("`reporter` is required"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SctraceBuilder<'_> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// The high-level API of syscall compatibility tracer.
|
||||
pub struct Sctrace<'a> {
|
||||
strace_iter: Lines<StraceLogStream>,
|
||||
patterns: Patterns<'a>,
|
||||
reporter: CliReporter,
|
||||
}
|
||||
|
||||
impl Sctrace<'_> {
|
||||
/// Runs the syscall trace analysis.
|
||||
pub fn run(mut self) -> Result<Option<Vec<String>>, String> {
|
||||
let matcher = Matcher::new(self.patterns.clone());
|
||||
|
||||
for line_result in &mut self.strace_iter {
|
||||
let line = line_result.map_err(|e| format!("Failed to read trace line: {}", e))?;
|
||||
|
||||
match Syscall::fetch(line) {
|
||||
Ok(line) => match Syscall::parse(&line) {
|
||||
Ok(syscall) => {
|
||||
if matcher.match_syscall(&syscall).is_some() {
|
||||
self.reporter.report_supported(&syscall);
|
||||
} else {
|
||||
self.reporter.report_unsupported(&syscall);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
self.reporter.report_parse_error(&line);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
match e {
|
||||
StraceParseError::BlockedLine
|
||||
| StraceParseError::SignalLine
|
||||
| StraceParseError::ExitLine
|
||||
| StraceParseError::EmptyLine => {
|
||||
// Ignore blocked, signal, exit and empty lines
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
panic!("Unexpected error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self.reporter.outputs())
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for creating a `CliReporter` with customizable settings.
|
||||
pub struct CliReporterBuilder {
|
||||
quiet: bool,
|
||||
collect: bool,
|
||||
}
|
||||
|
||||
impl CliReporterBuilder {
|
||||
/// Creates a new `CliReporterBuilder` instance.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
quiet: false,
|
||||
collect: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables quiet mode, suppressing supported syscall output.
|
||||
pub fn quiet(mut self) -> Self {
|
||||
self.quiet = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets quiet mode.
|
||||
pub fn set_quiet(mut self, quiet: bool) -> Self {
|
||||
self.quiet = quiet;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables collection of output strings instead of printing to stdout/stderr.
|
||||
pub fn collect(mut self) -> Self {
|
||||
self.collect = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets collection mode.
|
||||
pub fn set_collect(mut self, collect: bool) -> Self {
|
||||
self.collect = collect;
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the `CliReporter` instance with the specified settings.
|
||||
pub fn build(self) -> CliReporter {
|
||||
CliReporter::new(
|
||||
self.quiet,
|
||||
if self.collect { Some(Vec::new()) } else { None },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CliReporterBuilder {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Reporter for outputting syscall trace analysis results.
|
||||
pub struct CliReporter {
|
||||
quiet: bool,
|
||||
outputs: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
macro_rules! report {
|
||||
($vec:expr, err: $($arg:tt)*) => {
|
||||
if let Some(ref mut vec) = $vec {
|
||||
(&mut *vec).push(format!($($arg)*));
|
||||
} else {
|
||||
eprintln!($($arg)*);
|
||||
}
|
||||
};
|
||||
($vec:expr, $($arg:tt)*) => {
|
||||
if let Some(ref mut vec) = $vec {
|
||||
(&mut *vec).push(format!($($arg)*));
|
||||
} else {
|
||||
println!($($arg)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl CliReporter {
|
||||
fn new(quiet: bool, outputs: Option<Vec<String>>) -> Self {
|
||||
Self { quiet, outputs }
|
||||
}
|
||||
|
||||
fn report_supported(&mut self, syscall: &Syscall) {
|
||||
if !self.quiet {
|
||||
report!(self.outputs, "{}", syscall.original_line());
|
||||
}
|
||||
}
|
||||
|
||||
fn report_unsupported(&mut self, syscall: &Syscall) {
|
||||
if self.quiet {
|
||||
report!(
|
||||
self.outputs,
|
||||
err: "Unsupported syscall: {}",
|
||||
syscall.original_line()
|
||||
);
|
||||
} else {
|
||||
report!(
|
||||
self.outputs,
|
||||
err: "{} \x1b[31m(unsupported)\x1b[0m",
|
||||
syscall.original_line()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn report_parse_error(&mut self, line: &str) {
|
||||
report!(self.outputs, err: "Strace Parse Error: {}", line);
|
||||
}
|
||||
|
||||
fn outputs(self) -> Option<Vec<String>> {
|
||||
self.outputs
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
mod parameter;
|
||||
|
||||
use std::env;
|
||||
|
||||
use parameter::Parameters;
|
||||
use sctrace::{CliReporterBuilder, Patterns, SctraceBuilder, StraceLogStream};
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let params = Parameters::new(args)?;
|
||||
|
||||
let sctrace = SctraceBuilder::new()
|
||||
.patterns(Patterns::from_scml_files(params.scml_paths())?)
|
||||
.strace(if params.offline() {
|
||||
StraceLogStream::open_file(params.input_path())
|
||||
} else {
|
||||
StraceLogStream::run_cmd(params.program_path(), params.program_args())
|
||||
}?)
|
||||
.reporter(CliReporterBuilder::new().set_quiet(params.quiet()).build())
|
||||
.build();
|
||||
|
||||
let _ = sctrace.run();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("{}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
|
||||
/// Holds parsed command-line parameters for the sctrace tool.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Parameters {
|
||||
/// Path to the SCML pattern files
|
||||
scml_paths: Vec<String>,
|
||||
/// Path to the input strace log file (offline mode)
|
||||
input_path: String,
|
||||
/// Whether quiet mode is enabled
|
||||
quiet: bool,
|
||||
/// Path to the program to trace (online mode)
|
||||
program_path: String,
|
||||
/// Arguments to pass to the program (online mode)
|
||||
program_args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParameterError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParameterError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParameterError> for String {
|
||||
fn from(err: ParameterError) -> Self {
|
||||
err.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParameterError {}
|
||||
|
||||
impl Parameters {
|
||||
/// Creates a new Parameters instance from command line arguments.
|
||||
///
|
||||
/// ```text
|
||||
/// sctrace <SCML_PATH1> [SCML_PATH2...] [--input FILE] [--quiet] [-- PROGRAM] [PROGRAM_ARGS...]
|
||||
/// ```
|
||||
pub(crate) fn new(args: Vec<String>) -> Result<Self, ParameterError> {
|
||||
let app = Command::new("sctrace")
|
||||
.about("Syscall Compatibility Tracer (sctrace) - trace and validate syscalls against SCML patterns")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.after_help("EXAMPLES:\n \
|
||||
Offline mode: sctrace patterns.scml --input trace.log\n \
|
||||
Online mode: sctrace patterns.scml -- /bin/ls -la")
|
||||
.arg(
|
||||
Arg::new("scml_paths")
|
||||
.help("Path(s) to SCML file(s)")
|
||||
.required(true)
|
||||
.num_args(1..)
|
||||
.value_name("SCML_PATHS"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("input")
|
||||
.long("input")
|
||||
.help("Input file path")
|
||||
.value_name("FILE")
|
||||
.action(ArgAction::Set),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("quiet")
|
||||
.long("quiet")
|
||||
.help("Enable quiet mode")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("program")
|
||||
.help("Program to execute and its arguments (use -- to separate)")
|
||||
.num_args(0..)
|
||||
.value_name("PROGRAM")
|
||||
.last(true)
|
||||
.allow_hyphen_values(true),
|
||||
);
|
||||
|
||||
let matches = app.try_get_matches_from(args).map_err(|e| ParameterError {
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
|
||||
let scml_paths: Vec<String> = matches
|
||||
.get_many::<String>("scml_paths")
|
||||
.map(|values| values.cloned().collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let input_path = matches
|
||||
.get_one::<String>("input")
|
||||
.unwrap_or(&String::new())
|
||||
.clone();
|
||||
let quiet = matches.get_flag("quiet");
|
||||
|
||||
let mut program_iter = matches
|
||||
.get_many::<String>("program")
|
||||
.map(|values| values.cloned())
|
||||
.into_iter()
|
||||
.flatten();
|
||||
|
||||
let program_path = program_iter.next().unwrap_or_default();
|
||||
let program_args: Vec<String> = program_iter.collect();
|
||||
|
||||
let has_input = !input_path.is_empty();
|
||||
let has_program = !program_path.is_empty();
|
||||
|
||||
if has_input && has_program {
|
||||
return Err(ParameterError {
|
||||
message: "Cannot specify both --input and program arguments".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
if !has_input && !has_program {
|
||||
return Err(ParameterError {
|
||||
message: "Must specify either --input or program arguments".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Parameters {
|
||||
scml_paths,
|
||||
input_path,
|
||||
quiet,
|
||||
program_path,
|
||||
program_args,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn scml_paths(&self) -> &Vec<String> {
|
||||
&self.scml_paths
|
||||
}
|
||||
|
||||
pub(crate) fn input_path(&self) -> &str {
|
||||
&self.input_path
|
||||
}
|
||||
|
||||
pub(crate) fn quiet(&self) -> bool {
|
||||
self.quiet
|
||||
}
|
||||
|
||||
pub(crate) fn offline(&self) -> bool {
|
||||
!self.input_path.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn program_path(&self) -> &str {
|
||||
&self.program_path
|
||||
}
|
||||
|
||||
pub(crate) fn program_args(&self) -> Vec<&str> {
|
||||
self.program_args.iter().map(String::as_str).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::vec;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn to_string_vec(args: &[&str]) -> Vec<String> {
|
||||
args.iter().map(|s| s.to_string()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_offline_mode() {
|
||||
let args = to_string_vec(&["sctrace", "test.scml", "--input", "input.txt"]);
|
||||
let params = Parameters::new(args).unwrap();
|
||||
assert_eq!(params.scml_paths, vec!["test.scml"]);
|
||||
assert_eq!(params.input_path(), "input.txt");
|
||||
assert_eq!(params.quiet(), false);
|
||||
assert_eq!(params.program_path(), "");
|
||||
assert!(params.program_args().is_empty());
|
||||
assert!(params.offline());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_online_mode() {
|
||||
let args = to_string_vec(&["sctrace", "test.scml", "--", "my_program", "arg1", "arg2"]);
|
||||
let params = Parameters::new(args).unwrap();
|
||||
assert_eq!(params.scml_paths, vec!["test.scml"]);
|
||||
assert_eq!(params.input_path(), "");
|
||||
assert_eq!(params.quiet(), false);
|
||||
assert_eq!(params.program_path(), "my_program");
|
||||
assert_eq!(params.program_args(), &["arg1", "arg2"]);
|
||||
assert!(!params.offline());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_online_mode_no_args() {
|
||||
let args = to_string_vec(&["sctrace", "test.scml", "--", "my_program"]);
|
||||
let params = Parameters::new(args).unwrap();
|
||||
assert_eq!(params.scml_paths, vec!["test.scml"]);
|
||||
assert_eq!(params.program_path(), "my_program");
|
||||
assert!(params.program_args().is_empty());
|
||||
assert!(!params.offline());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_quiet_mode() {
|
||||
let args = to_string_vec(&["sctrace", "test.scml", "--quiet", "--input", "input.txt"]);
|
||||
let params = Parameters::new(args).unwrap();
|
||||
assert!(params.quiet());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_missing_scml_file() {
|
||||
let args = to_string_vec(&["sctrace", "--input", "input.txt"]);
|
||||
let params = Parameters::new(args);
|
||||
assert!(params.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_missing_input_and_program() {
|
||||
let args = to_string_vec(&["sctrace", "test.scml"]);
|
||||
let result = Parameters::new(args);
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.err().unwrap().to_string(),
|
||||
"Must specify either --input or program arguments"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_many_scml_paths() {
|
||||
let args = to_string_vec(&["sctrace", "--input", "in.txt", "a.scml", "b.scml", "c.scml"]);
|
||||
let params = Parameters::new(args).unwrap();
|
||||
assert_eq!(params.scml_paths, vec!["a.scml", "b.scml", "c.scml"]);
|
||||
|
||||
let args = to_string_vec(&["sctrace", "a.scml", "b.scml", "c.scml", "--", "prog", "arg"]);
|
||||
let params = Parameters::new(args).unwrap();
|
||||
assert_eq!(params.scml_paths, vec!["a.scml", "b.scml", "c.scml"]);
|
||||
assert_eq!(params.program_path(), "prog");
|
||||
assert_eq!(params.program_args(), &["arg"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_new_both_input_and_program() {
|
||||
let args = to_string_vec(&[
|
||||
"sctrace", "--input", "in.txt", "a.scml", "b.scml", "c.scml", "--", "prog",
|
||||
]);
|
||||
let result = Parameters::new(args);
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.err().unwrap().to_string(),
|
||||
"Cannot specify both --input and program arguments"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use crate::{
|
||||
scml_parser::{
|
||||
ParserCtx, Pattern, PatternArg, PatternArray, PatternFlagSet, PatternStruct, Patterns,
|
||||
},
|
||||
strace_parser::{Syscall, SyscallArg, SyscallArray, SyscallFlagSet, SyscallStruct},
|
||||
};
|
||||
|
||||
/// Matcher for syscalls against SCML patterns.
|
||||
pub(crate) struct Matcher<'a> {
|
||||
patterns: Patterns<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Matcher<'a> {
|
||||
pub(crate) fn new(patterns: Patterns<'a>) -> Self {
|
||||
Self { patterns }
|
||||
}
|
||||
|
||||
/// Attempts to match a syscall against available patterns.
|
||||
pub(crate) fn match_syscall(&self, syscall: &Syscall) -> Option<&Pattern<'_>> {
|
||||
let related_patterns = self.patterns.get(syscall.name())?;
|
||||
|
||||
related_patterns
|
||||
.iter()
|
||||
.find(|pattern| Self::matches(self.patterns.ctx(), syscall, pattern))
|
||||
}
|
||||
|
||||
/// Matches a syscall against a specific pattern.
|
||||
fn matches(ctx: &ParserCtx, syscall: &Syscall, pattern: &Pattern) -> bool {
|
||||
assert_eq!(syscall.name(), pattern.name());
|
||||
|
||||
// Argument count must match exactly if wildcard is not enabled
|
||||
if !pattern.wildcard() && syscall.args().len() != pattern.args().len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// syscall arguments must be at least as many as pattern arguments
|
||||
if syscall.args().len() < pattern.args().len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Match each argument pair
|
||||
for (syscall_arg, pattern_arg) in syscall.args().iter().zip(pattern.args().iter()) {
|
||||
if !Self::matches_arg(ctx, syscall_arg, pattern_arg) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Matches a single syscall argument against a pattern argument.
|
||||
fn matches_arg(ctx: &ParserCtx, syscall_arg: &SyscallArg, pattern_arg: &PatternArg) -> bool {
|
||||
match pattern_arg {
|
||||
// Matches any syscall argument
|
||||
PatternArg::None => true,
|
||||
|
||||
// Type constraint - matches only integer arguments
|
||||
PatternArg::Integer => matches!(syscall_arg, SyscallArg::Integer(_)),
|
||||
|
||||
// Path matching not yet implemented
|
||||
PatternArg::Path => todo!("Path matching not implemented yet"),
|
||||
|
||||
// Flag set matching - supports both zero values and flag combinations
|
||||
PatternArg::Flags(pattern_flags) => match syscall_arg {
|
||||
// Special case: integer zero matches any flag pattern
|
||||
SyscallArg::Integer(value) if *value == "0" => true,
|
||||
// Validate that all syscall flags are present in pattern
|
||||
SyscallArg::Flags(syscall_flags) => {
|
||||
Self::matches_flags(ctx, syscall_flags, pattern_flags)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
|
||||
// Array matching - recursive element validation
|
||||
PatternArg::Array(pattern_array) => match syscall_arg {
|
||||
SyscallArg::Array(syscall_array) => {
|
||||
Self::matches_array(ctx, syscall_array, pattern_array)
|
||||
}
|
||||
// Handle NULL array case - single Flag("NULL") matches any array pattern
|
||||
SyscallArg::Flags(flag_set) if flag_set.flags().len() == 1 => {
|
||||
matches!(
|
||||
flag_set.flags().first(),
|
||||
Some(SyscallArg::Flag(flag_name)) if *flag_name == "NULL"
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
|
||||
// Struct matching - field-by-field validation with wildcard support
|
||||
PatternArg::Struct(pattern_struct) => match syscall_arg {
|
||||
SyscallArg::Struct(syscall_struct) => {
|
||||
Self::matches_struct(ctx, syscall_struct, pattern_struct)
|
||||
}
|
||||
// Handle NULL pointer case - single Flag("NULL") matches any struct pattern
|
||||
SyscallArg::Flags(flag_set) if flag_set.flags().len() == 1 => {
|
||||
matches!(
|
||||
flag_set.flags().first(),
|
||||
Some(SyscallArg::Flag(flag_name)) if *flag_name == "NULL"
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
|
||||
// Multiple struct alternatives - matches if any alternative matches
|
||||
PatternArg::MultipleStruct(pattern_structs) => match syscall_arg {
|
||||
SyscallArg::Struct(syscall_struct) => {
|
||||
pattern_structs.structs().iter().any(|pattern_struct| {
|
||||
if let PatternArg::Struct(pattern_struct) = pattern_struct {
|
||||
Self::matches_struct(ctx, syscall_struct, pattern_struct)
|
||||
} else {
|
||||
panic!("Expected PatternArg::Struct inside MultipleStruct");
|
||||
}
|
||||
})
|
||||
}
|
||||
// Handle NULL pointer case - single Flag("NULL") matches any struct pattern
|
||||
SyscallArg::Flags(flag_set) if flag_set.flags().len() == 1 => {
|
||||
matches!(
|
||||
flag_set.flags().first(),
|
||||
Some(SyscallArg::Flag(flag_name)) if *flag_name == "NULL"
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
|
||||
PatternArg::FlagsVariable(_) | PatternArg::StructVariable(_) => {
|
||||
let expanded = pattern_arg.get(ctx);
|
||||
Self::matches_arg(ctx, syscall_arg, expanded)
|
||||
}
|
||||
|
||||
// Single flags should not appear in patterns
|
||||
PatternArg::Flag(_) => {
|
||||
panic!(
|
||||
"Single Flag should not appear in pattern matching - should be wrapped in Flags"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn matches_flags(
|
||||
ctx: &ParserCtx,
|
||||
syscall_flags: &SyscallFlagSet,
|
||||
pattern_flags: &PatternFlagSet,
|
||||
) -> bool {
|
||||
// Every syscall flag must find a match in the pattern flags
|
||||
syscall_flags
|
||||
.flags()
|
||||
.iter()
|
||||
.all(|syscall_flag| Self::matches_flag(ctx, syscall_flag, pattern_flags))
|
||||
}
|
||||
|
||||
/// Matches a single syscall flag against a pattern flag set.
|
||||
fn matches_flag(
|
||||
ctx: &ParserCtx,
|
||||
syscall_flag: &SyscallArg,
|
||||
pattern_flags: &PatternFlagSet,
|
||||
) -> bool {
|
||||
match syscall_flag {
|
||||
SyscallArg::Flag(_) => (),
|
||||
_ => {
|
||||
panic!("Syscall flag must be of type Flag for flag matching");
|
||||
}
|
||||
};
|
||||
|
||||
let mut found_match = false;
|
||||
|
||||
for pattern_flag in pattern_flags.flags() {
|
||||
match (syscall_flag, pattern_flag) {
|
||||
// Named flag matching - exact name comparison
|
||||
(SyscallArg::Flag(syscall_name), PatternArg::Flag(pattern_name)) => {
|
||||
if syscall_name == pattern_name {
|
||||
found_match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Integer flag matching - pattern accepts any integer
|
||||
(SyscallArg::Integer(_), PatternArg::Integer) => {
|
||||
found_match = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Flags variable - expand and attempt matching
|
||||
(_, PatternArg::FlagsVariable(_)) => {
|
||||
if let PatternArg::Flags(expanded) = pattern_flag.get(ctx) {
|
||||
found_match = Self::matches_flag(ctx, syscall_flag, expanded);
|
||||
if found_match {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
found_match
|
||||
}
|
||||
|
||||
fn matches_array(
|
||||
ctx: &ParserCtx,
|
||||
syscall_array: &SyscallArray,
|
||||
pattern_array: &PatternArray,
|
||||
) -> bool {
|
||||
syscall_array.elements().iter().all(|syscall_element| {
|
||||
pattern_array
|
||||
.args()
|
||||
.iter()
|
||||
.any(|pattern_element| Self::matches_arg(ctx, syscall_element, pattern_element))
|
||||
})
|
||||
}
|
||||
|
||||
fn matches_struct(
|
||||
ctx: &ParserCtx,
|
||||
syscall_struct: &SyscallStruct,
|
||||
pattern_struct: &PatternStruct,
|
||||
) -> bool {
|
||||
// Without wildcard, field count must match exactly
|
||||
if !pattern_struct.wildcard()
|
||||
&& syscall_struct.fields().len() != pattern_struct.fields().len()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Every pattern field must be found and matched in the syscall struct
|
||||
for (pattern_field_name, pattern_field_value) in pattern_struct.fields() {
|
||||
match syscall_struct.get_value(pattern_field_name) {
|
||||
Some(syscall_field_value) => {
|
||||
if !Self::matches_arg(ctx, syscall_field_value, pattern_field_value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Required pattern field not found in syscall
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,824 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{collections::HashMap, error::Error, fmt, fs, path::Path};
|
||||
|
||||
use nom::{
|
||||
IResult,
|
||||
branch::alt,
|
||||
bytes::complete::{tag, take_while},
|
||||
character::complete::{char, multispace0},
|
||||
combinator::{opt, recognize},
|
||||
multi::{separated_list0, separated_list1},
|
||||
sequence::{delimited, pair, preceded},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ScmlParseError {
|
||||
IoError(String),
|
||||
ParseError(String),
|
||||
IncompleteStatement(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for ScmlParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ScmlParseError::IoError(msg) => write!(f, "I/O error: {}", msg),
|
||||
ScmlParseError::ParseError(msg) => write!(f, "Parsing error: {}", msg),
|
||||
ScmlParseError::IncompleteStatement(stmt) => {
|
||||
write!(f, "Incomplete statement: {}", stmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScmlParseError> for String {
|
||||
fn from(err: ScmlParseError) -> Self {
|
||||
err.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ScmlParseError {}
|
||||
|
||||
/// Collection of syscall patterns parsed from SCML files.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Patterns<'a> {
|
||||
/// Map from syscall names to their associated patterns.
|
||||
patterns: HashMap<&'a str, Vec<Pattern<'a>>>,
|
||||
|
||||
/// Parser context with variable definitions.
|
||||
ctx: ParserCtx<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Patterns<'a> {
|
||||
/// Reads SCML file(s) and parses all pattern and variable definitions.
|
||||
pub fn from_scml_files<P: AsRef<Path>>(paths: &Vec<P>) -> Result<Self, ScmlParseError> {
|
||||
let mut all_patterns = Patterns::default();
|
||||
|
||||
for path in paths {
|
||||
let patterns = {
|
||||
let content = fs::read_to_string(path).map_err(|e| {
|
||||
ScmlParseError::IoError(format!(
|
||||
"Failed to read file '{}': {}",
|
||||
path.as_ref().display(),
|
||||
e
|
||||
))
|
||||
})?;
|
||||
// Using the file path as variable prefix to avoid name clashes
|
||||
Self::from_scml_with_var_prefix(path.as_ref().display().to_string(), &content)?
|
||||
};
|
||||
|
||||
all_patterns.merge(patterns);
|
||||
}
|
||||
|
||||
Ok(all_patterns)
|
||||
}
|
||||
|
||||
/// Parses SCML content into patterns.
|
||||
pub fn from_scml(content: &str) -> Result<Self, ScmlParseError> {
|
||||
Self::from_scml_with_var_prefix("".to_string(), content)
|
||||
}
|
||||
|
||||
/// Retrieves all patterns for a specific syscall name.
|
||||
pub(crate) fn get(&self, name: &str) -> Option<&Vec<Pattern<'_>>> {
|
||||
self.patterns.get(name)
|
||||
}
|
||||
|
||||
/// Returns the parser context with variable definitions.
|
||||
pub(crate) fn ctx(&self) -> &ParserCtx<'_> {
|
||||
&self.ctx
|
||||
}
|
||||
|
||||
fn new(patterns: HashMap<&'a str, Vec<Pattern<'a>>>, ctx: ParserCtx<'a>) -> Self {
|
||||
Self { patterns, ctx }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Patterns<'_> {
|
||||
fn default() -> Self {
|
||||
Self::new(HashMap::new(), ParserCtx::new())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct Pattern<'a> {
|
||||
/// Name of the pattern (corresponds to syscall name).
|
||||
name: &'a str,
|
||||
|
||||
/// Ordered list of argument patterns for this syscall.
|
||||
args: Vec<PatternArg<'a>>,
|
||||
|
||||
/// Whether this pattern accepts additional unspecified arguments.
|
||||
wildcard: bool,
|
||||
}
|
||||
|
||||
impl<'a> Pattern<'a> {
|
||||
pub(crate) fn name(&self) -> &'a str {
|
||||
self.name
|
||||
}
|
||||
|
||||
pub(crate) fn args(&self) -> &Vec<PatternArg<'a>> {
|
||||
&self.args
|
||||
}
|
||||
|
||||
pub(crate) fn wildcard(&self) -> bool {
|
||||
self.wildcard
|
||||
}
|
||||
|
||||
fn new(name: &'a str, args: Vec<PatternArg<'a>>, wildcard: bool) -> Self {
|
||||
Self {
|
||||
name,
|
||||
args,
|
||||
wildcard,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(ctx: &ParserCtxBuilder<'a>, input: &'a str) -> IResult<&'a str, Pattern<'a>> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, name) = identifier(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char('(')(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, args) = opt(|i| Self::parse_param_list(ctx, i))(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = opt(char(','))(input)?; // Allow trailing comma
|
||||
let (input, has_wildcard) = opt(preceded(
|
||||
multispace0,
|
||||
delimited(multispace0, tag(".."), multispace0),
|
||||
))(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char(')')(input)?;
|
||||
let (input, _) = char(';')(input)?;
|
||||
|
||||
let args = args.unwrap_or_default();
|
||||
Ok((input, Pattern::new(name, args, has_wildcard.is_some())))
|
||||
}
|
||||
|
||||
fn parse_definition(ctx: &mut ParserCtxBuilder<'a>, input: &'a str) -> IResult<&'a str, ()> {
|
||||
if let Ok((input, (name, flags))) = Self::parse_flags_definition(ctx, input) {
|
||||
ctx.insert_flags_variable(name, flags);
|
||||
return Ok((input, ()));
|
||||
}
|
||||
|
||||
if let Ok((input, (name, struct_def))) = Self::parse_struct_definition(ctx, input) {
|
||||
ctx.insert_struct_variable(name, struct_def);
|
||||
return Ok((input, ()));
|
||||
}
|
||||
|
||||
ctx.set_last_struct(None);
|
||||
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
input,
|
||||
nom::error::ErrorKind::Tag,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum PatternArg<'a> {
|
||||
/// Matches all values (no constraint).
|
||||
None,
|
||||
|
||||
/// Matches integer arguments.
|
||||
Integer,
|
||||
|
||||
/// Matches path arguments.
|
||||
Path,
|
||||
|
||||
/// Matches specific flag values like `O_RDONLY`.
|
||||
Flag(&'a str),
|
||||
|
||||
/// Matches array arguments where each element matches the corresponding
|
||||
/// pattern in the array.
|
||||
Array(PatternArray<'a>),
|
||||
|
||||
/// Matches struct arguments with specified field constraints.
|
||||
Struct(PatternStruct<'a>),
|
||||
|
||||
/// Matches if any of the struct patterns match. Used when a struct
|
||||
/// variable is defined multiple times with different field combinations.
|
||||
MultipleStruct(PatternMultipleStruct<'a>),
|
||||
|
||||
/// Matches flag combinations like `O_RDWR | O_CREAT`.
|
||||
Flags(PatternFlagSet<'a>),
|
||||
|
||||
/// Contains the variable ID that resolves to a `PatternFlagSet`.
|
||||
FlagsVariable(&'a str),
|
||||
|
||||
/// Contains the variable ID that resolves to a `PatternStruct`
|
||||
/// or `PatternMultipleStruct`.
|
||||
StructVariable(&'a str),
|
||||
}
|
||||
|
||||
impl PatternArg<'_> {
|
||||
/// Dereferences `FlagsVariable` and `StructVariable` types to
|
||||
/// retrieve their actual pattern definitions from the parser context.
|
||||
pub(crate) fn get<'b>(&self, ctx: &'b ParserCtx) -> &'b PatternArg<'b> {
|
||||
match self {
|
||||
PatternArg::FlagsVariable(id) => ctx.flags_lookup(id).unwrap(),
|
||||
|
||||
PatternArg::StructVariable(id) => ctx.struct_lookup(id).unwrap(),
|
||||
|
||||
_ => {
|
||||
panic!("get() can only be called on variable reference types");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An array pattern matches array arguments where each element must match
|
||||
/// one of the specified patterns in the array.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct PatternArray<'a>(Vec<PatternArg<'a>>);
|
||||
|
||||
impl<'a> PatternArray<'a> {
|
||||
pub(crate) fn args(&self) -> &Vec<PatternArg<'a>> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn new(args: Vec<PatternArg<'a>>) -> Self {
|
||||
Self(args)
|
||||
}
|
||||
}
|
||||
|
||||
/// A flag set pattern matches bitwise OR combinations of flags.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct PatternFlagSet<'a>(Vec<PatternArg<'a>>);
|
||||
|
||||
impl<'a> PatternFlagSet<'a> {
|
||||
pub(crate) fn flags(&self) -> &Vec<PatternArg<'a>> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn new(flags: Vec<PatternArg<'a>>) -> Self {
|
||||
// Validate that all elements are either Flag or Integer types
|
||||
for flag in &flags {
|
||||
match flag {
|
||||
PatternArg::Flag(_) | PatternArg::Integer | PatternArg::FlagsVariable(_) => {
|
||||
// Valid flag type
|
||||
}
|
||||
_ => {
|
||||
panic!("PatternFlagSet can only contain Flag or Integer types");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self(flags)
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct pattern matches structured data with named fields. It can specify
|
||||
/// exact field matching or allow additional fields via wildcard.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct PatternStruct<'a> {
|
||||
fields: HashMap<&'a str, PatternArg<'a>>,
|
||||
wildcard: bool,
|
||||
}
|
||||
|
||||
impl<'a> PatternStruct<'a> {
|
||||
pub(crate) fn fields(&self) -> &HashMap<&'a str, PatternArg<'a>> {
|
||||
&self.fields
|
||||
}
|
||||
|
||||
pub(crate) fn wildcard(&self) -> bool {
|
||||
self.wildcard
|
||||
}
|
||||
|
||||
fn new(fields: HashMap<&'a str, PatternArg<'a>>, wildcard: bool) -> Self {
|
||||
Self { fields, wildcard }
|
||||
}
|
||||
}
|
||||
|
||||
/// A multiple struct pattern allows defining a struct variable multiple times
|
||||
/// with different field combinations. The pattern matches if any of the alternatives
|
||||
/// match.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct PatternMultipleStruct<'a>(Vec<PatternArg<'a>>);
|
||||
|
||||
impl<'a> PatternMultipleStruct<'a> {
|
||||
pub(crate) fn structs(&self) -> &Vec<PatternArg<'a>> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn new(structs: Vec<PatternArg<'a>>) -> Self {
|
||||
for struct_arg in &structs {
|
||||
match struct_arg {
|
||||
PatternArg::Struct(_) => {
|
||||
// Valid struct type
|
||||
}
|
||||
_ => {
|
||||
panic!("PatternMultipleStruct can only contain Struct types");
|
||||
}
|
||||
}
|
||||
}
|
||||
Self(structs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct ParserCtx<'a> {
|
||||
/// Map of flags variable IDs to their pattern definitions.
|
||||
flags_variables: HashMap<&'a str, PatternArg<'a>>,
|
||||
/// Map of struct variable IDs to their pattern definitions.
|
||||
struct_variables: HashMap<&'a str, PatternArg<'a>>,
|
||||
/// Map of multiple struct variable IDs to their pattern definitions.
|
||||
multiple_struct_variables: HashMap<&'a str, PatternArg<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> ParserCtx<'a> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
flags_variables: HashMap::new(),
|
||||
struct_variables: HashMap::new(),
|
||||
multiple_struct_variables: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn flags_lookup(&self, id: &str) -> Option<&PatternArg<'a>> {
|
||||
self.flags_variables.get(id)
|
||||
}
|
||||
|
||||
fn struct_lookup(&self, id: &str) -> Option<&PatternArg<'a>> {
|
||||
if let Some(pattern) = self.struct_variables.get(id) {
|
||||
Some(pattern)
|
||||
} else if let Some(pattern) = self.multiple_struct_variables.get(id) {
|
||||
Some(pattern)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct ParserCtxBuilder<'a> {
|
||||
variable_prefix: String,
|
||||
/// The parser context being built.
|
||||
parser_ctx: ParserCtx<'a>,
|
||||
/// Map of named bitflags to their internal IDs.
|
||||
named_bitflags: HashMap<&'a str, &'a str>,
|
||||
/// Map of named structs to their internal IDs.
|
||||
named_structs: HashMap<&'a str, &'a str>,
|
||||
/// Last parsed struct definition.
|
||||
last_struct: Option<(&'a str, &'a str)>,
|
||||
/// Counter for generating unique variable IDs.
|
||||
counter: usize,
|
||||
}
|
||||
|
||||
impl<'a> ParserCtxBuilder<'a> {
|
||||
fn new(name: String) -> Self {
|
||||
Self {
|
||||
variable_prefix: name,
|
||||
parser_ctx: ParserCtx::new(),
|
||||
named_bitflags: HashMap::new(),
|
||||
named_structs: HashMap::new(),
|
||||
last_struct: None,
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn build(self) -> ParserCtx<'a> {
|
||||
self.parser_ctx
|
||||
}
|
||||
|
||||
fn generate_variable_id(&mut self) -> &'static str {
|
||||
self.counter += 1;
|
||||
Box::leak(Box::new(format!(
|
||||
"{}/var_{}",
|
||||
self.variable_prefix, self.counter
|
||||
)))
|
||||
}
|
||||
|
||||
fn flags_variables_mut(&mut self) -> &mut HashMap<&'a str, PatternArg<'a>> {
|
||||
&mut self.parser_ctx.flags_variables
|
||||
}
|
||||
|
||||
fn struct_variables_mut(&mut self) -> &mut HashMap<&'a str, PatternArg<'a>> {
|
||||
&mut self.parser_ctx.struct_variables
|
||||
}
|
||||
|
||||
fn multiple_struct_variables_mut(&mut self) -> &mut HashMap<&'a str, PatternArg<'a>> {
|
||||
&mut self.parser_ctx.multiple_struct_variables
|
||||
}
|
||||
|
||||
fn insert_named_bitflag(&mut self, name: &'a str, id: &'a str) {
|
||||
self.named_bitflags.insert(name, id);
|
||||
}
|
||||
|
||||
fn insert_named_struct(&mut self, name: &'a str, id: &'a str) {
|
||||
self.named_structs.insert(name, id);
|
||||
}
|
||||
|
||||
fn add_flags_variable(&mut self, id: &'a str, flags: PatternArg<'a>) {
|
||||
self.flags_variables_mut().insert(id, flags);
|
||||
}
|
||||
|
||||
fn add_struct_variable(&mut self, id: &'a str, struct_def: PatternArg<'a>) {
|
||||
self.struct_variables_mut().insert(id, struct_def);
|
||||
}
|
||||
|
||||
fn add_multiple_struct_variable(&mut self, id: &'a str, struct_def: PatternArg<'a>) {
|
||||
if self.multiple_struct_variables_mut().contains_key(id) {
|
||||
self.append_to_multiple_struct(id, struct_def);
|
||||
} else {
|
||||
self.convert_struct_to_multiple(id);
|
||||
self.append_to_multiple_struct(id, struct_def);
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_struct_to_multiple(&mut self, id: &'a str) {
|
||||
if let Some(existing_struct) = self.struct_variables_mut().remove(id) {
|
||||
let multiple_struct =
|
||||
PatternArg::MultipleStruct(PatternMultipleStruct::new(vec![existing_struct]));
|
||||
self.multiple_struct_variables_mut()
|
||||
.insert(id, multiple_struct);
|
||||
} else {
|
||||
panic!("Struct variable should exist for conversion");
|
||||
}
|
||||
}
|
||||
|
||||
fn append_to_multiple_struct(&mut self, id: &str, struct_def: PatternArg<'a>) {
|
||||
let multiple_struct = self
|
||||
.parser_ctx
|
||||
.multiple_struct_variables
|
||||
.get_mut(id)
|
||||
.expect("Multiple struct variable should exist");
|
||||
|
||||
if let PatternArg::MultipleStruct(multi_struct) = multiple_struct {
|
||||
multi_struct.0.push(struct_def);
|
||||
} else {
|
||||
panic!("Expected MultipleStruct variant");
|
||||
}
|
||||
}
|
||||
|
||||
fn set_last_struct(&mut self, value: Option<(&'a str, &'a str)>) {
|
||||
self.last_struct = value;
|
||||
}
|
||||
|
||||
fn get_flags_id(&self, name: &str) -> Option<&'a str> {
|
||||
self.named_bitflags.get(name).copied()
|
||||
}
|
||||
|
||||
fn get_struct_id(&self, name: &str) -> Option<&'a str> {
|
||||
self.named_structs.get(name).copied()
|
||||
}
|
||||
|
||||
fn insert_flags_variable(&mut self, name: &'a str, flags: PatternArg<'a>) {
|
||||
let id = self.generate_variable_id();
|
||||
self.insert_named_bitflag(name, id);
|
||||
self.add_flags_variable(id, flags);
|
||||
self.set_last_struct(None);
|
||||
}
|
||||
|
||||
fn insert_struct_variable(&mut self, name: &'a str, struct_def: PatternArg<'a>) {
|
||||
if let Some((last_struct_name, last_struct_id)) = &self.last_struct {
|
||||
// Continuous definition - append as alternative
|
||||
if &name == last_struct_name {
|
||||
self.add_multiple_struct_variable(last_struct_id, struct_def);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// New struct definition
|
||||
let id = self.generate_variable_id();
|
||||
self.insert_named_struct(name, id);
|
||||
self.add_struct_variable(id, struct_def);
|
||||
self.set_last_struct(Some((name, id)));
|
||||
}
|
||||
}
|
||||
|
||||
fn identifier(input: &str) -> IResult<&str, &str> {
|
||||
alt((
|
||||
nom::character::complete::digit1,
|
||||
recognize(pair(
|
||||
alt((nom::character::complete::alpha1, tag("_"))),
|
||||
take_while(|c: char| c.is_alphanumeric() || c == '_'),
|
||||
)),
|
||||
))(input)
|
||||
}
|
||||
|
||||
impl Patterns<'_> {
|
||||
/// Get patterns from SCML content with a specific variable prefix.
|
||||
fn from_scml_with_var_prefix(
|
||||
variable_prefix: String,
|
||||
content: &str,
|
||||
) -> Result<Self, ScmlParseError> {
|
||||
let stmt_iterator = StatementIterator::new(content);
|
||||
let mut patterns: HashMap<&str, Vec<Pattern>> = HashMap::new();
|
||||
let mut ctx = ParserCtxBuilder::new(variable_prefix);
|
||||
let mut errors = Vec::new();
|
||||
|
||||
for stmt in stmt_iterator {
|
||||
let statement = stmt?;
|
||||
|
||||
if Pattern::parse_definition(&mut ctx, statement).is_ok() {
|
||||
// Successfully parsed a definition, continue to next statement
|
||||
continue;
|
||||
}
|
||||
|
||||
match Pattern::parse(&ctx, statement) {
|
||||
Ok((remaining, pattern)) => {
|
||||
if !remaining.trim().is_empty() {
|
||||
errors.push(format!(
|
||||
"Warning: Unparsed input remaining in statement '{}': '{}'",
|
||||
statement, remaining
|
||||
));
|
||||
}
|
||||
patterns.entry(pattern.name()).or_default().push(pattern);
|
||||
}
|
||||
Err(err) => {
|
||||
errors.push(format!("Error parsing statement '{}': {}", statement, err));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(ScmlParseError::ParseError(errors.join("\n")));
|
||||
}
|
||||
|
||||
Ok(Self::new(patterns, ctx.build()))
|
||||
}
|
||||
|
||||
/// Merges another Patterns instance into self.
|
||||
fn merge(&mut self, other: Self) {
|
||||
for (syscall_name, mut other_patterns) in other.patterns {
|
||||
self.patterns
|
||||
.entry(syscall_name)
|
||||
.or_default()
|
||||
.append(&mut other_patterns);
|
||||
}
|
||||
|
||||
self.ctx.flags_variables.extend(other.ctx.flags_variables);
|
||||
self.ctx.struct_variables.extend(other.ctx.struct_variables);
|
||||
self.ctx
|
||||
.multiple_struct_variables
|
||||
.extend(other.ctx.multiple_struct_variables);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Pattern<'a> {
|
||||
fn parse_param_list(
|
||||
ctx: &ParserCtxBuilder<'a>,
|
||||
input: &'a str,
|
||||
) -> IResult<&'a str, Vec<PatternArg<'a>>> {
|
||||
separated_list0(delimited(multispace0, char(','), multispace0), |i| {
|
||||
Self::parse_param(ctx, i)
|
||||
})(input)
|
||||
}
|
||||
|
||||
fn parse_param(ctx: &ParserCtxBuilder<'a>, input: &'a str) -> IResult<&'a str, PatternArg<'a>> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = identifier(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
|
||||
// Check if parameter has constraint (= value)
|
||||
if let Ok((input, _)) = char::<&str, nom::error::Error<&str>>('=')(input) {
|
||||
let (input, _) = multispace0(input)?;
|
||||
Self::parse_expr(ctx, input)
|
||||
} else {
|
||||
// Unconstrained parameter
|
||||
Ok((input, PatternArg::None))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr(ctx: &ParserCtxBuilder<'a>, input: &'a str) -> IResult<&'a str, PatternArg<'a>> {
|
||||
alt((
|
||||
|i| Self::parse_struct(ctx, i),
|
||||
|i| Self::parse_array(ctx, i),
|
||||
Self::parse_builtin_type,
|
||||
|i| Self::parse_flags(ctx, i),
|
||||
|i| Self::parse_struct_variable(ctx, i),
|
||||
|i| Self::parse_flags_variable(ctx, i),
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn parse_struct(
|
||||
ctx: &ParserCtxBuilder<'a>,
|
||||
input: &'a str,
|
||||
) -> IResult<&'a str, PatternArg<'a>> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char('{')(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
|
||||
let (input, fields) =
|
||||
separated_list0(delimited(multispace0, char(','), multispace0), |i| {
|
||||
Self::parse_struct_field(ctx, i)
|
||||
})(input)?;
|
||||
|
||||
let (input, _) = multispace0(input)?;
|
||||
|
||||
// Check for wildcard (..)
|
||||
let (input, wildcard) = opt(preceded(
|
||||
opt(char(',')),
|
||||
delimited(multispace0, tag(".."), multispace0),
|
||||
))(input)?;
|
||||
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char('}')(input)?;
|
||||
|
||||
let mut field_map = HashMap::new();
|
||||
for (name, arg) in fields {
|
||||
field_map.insert(name, arg);
|
||||
}
|
||||
|
||||
let has_wildcard = wildcard.is_some();
|
||||
Ok((
|
||||
input,
|
||||
PatternArg::Struct(PatternStruct::new(field_map, has_wildcard)),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_struct_field(
|
||||
ctx: &ParserCtxBuilder<'a>,
|
||||
input: &'a str,
|
||||
) -> IResult<&'a str, (&'a str, PatternArg<'a>)> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, name) = identifier(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
|
||||
if let Ok((input, _)) = char::<&str, nom::error::Error<&str>>('=')(input) {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, expr) = Self::parse_expr(ctx, input)?;
|
||||
Ok((input, (name, expr)))
|
||||
} else {
|
||||
Ok((input, (name, PatternArg::None)))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_array(ctx: &ParserCtxBuilder<'a>, input: &'a str) -> IResult<&'a str, PatternArg<'a>> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char('[')(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, elements) =
|
||||
separated_list0(delimited(multispace0, char(','), multispace0), |i| {
|
||||
Self::parse_expr(ctx, i)
|
||||
})(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char(']')(input)?;
|
||||
|
||||
Ok((input, PatternArg::Array(PatternArray::new(elements))))
|
||||
}
|
||||
|
||||
fn parse_flags(ctx: &ParserCtxBuilder<'a>, input: &'a str) -> IResult<&'a str, PatternArg<'a>> {
|
||||
let (input, flags) = separated_list1(
|
||||
delimited(multispace0, char('|'), multispace0),
|
||||
alt((Self::parse_builtin_type, Self::parse_flag, |i| {
|
||||
Self::parse_flags_variable(ctx, i)
|
||||
})),
|
||||
)(input)?;
|
||||
|
||||
Ok((input, PatternArg::Flags(PatternFlagSet::new(flags))))
|
||||
}
|
||||
|
||||
fn parse_builtin_type(input: &'a str) -> IResult<&'a str, PatternArg<'a>> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char('<')(input)?;
|
||||
let (input, type_name) = identifier(input)?;
|
||||
let (input, _) = char('>')(input)?;
|
||||
|
||||
match type_name {
|
||||
"INTEGER" => Ok((input, PatternArg::Integer)),
|
||||
"PATH" => Ok((input, PatternArg::Path)),
|
||||
_ => Err(nom::Err::Error(nom::error::Error::new(
|
||||
input,
|
||||
nom::error::ErrorKind::Tag,
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_flags_variable(
|
||||
ctx: &ParserCtxBuilder<'a>,
|
||||
input: &'a str,
|
||||
) -> IResult<&'a str, PatternArg<'a>> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char('<')(input)?;
|
||||
let (input, var_name) = identifier(input)?;
|
||||
let (input, _) = char('>')(input)?;
|
||||
|
||||
if ctx.get_flags_id(var_name).is_none() {
|
||||
return Err(nom::Err::Error(nom::error::Error::new(
|
||||
input,
|
||||
nom::error::ErrorKind::Tag,
|
||||
)));
|
||||
}
|
||||
|
||||
Ok((
|
||||
input,
|
||||
PatternArg::FlagsVariable(ctx.get_flags_id(var_name).unwrap()),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_struct_variable(
|
||||
ctx: &ParserCtxBuilder<'a>,
|
||||
input: &'a str,
|
||||
) -> IResult<&'a str, PatternArg<'a>> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char('<')(input)?;
|
||||
let (input, var_name) = identifier(input)?;
|
||||
let (input, _) = char('>')(input)?;
|
||||
|
||||
if ctx.get_struct_id(var_name).is_none() {
|
||||
return Err(nom::Err::Error(nom::error::Error::new(
|
||||
input,
|
||||
nom::error::ErrorKind::Tag,
|
||||
)));
|
||||
}
|
||||
|
||||
Ok((
|
||||
input,
|
||||
PatternArg::StructVariable(ctx.get_struct_id(var_name).unwrap()),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_flag(input: &'a str) -> IResult<&'a str, PatternArg<'a>> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, flag_name) = identifier(input)?;
|
||||
Ok((input, PatternArg::Flag(flag_name)))
|
||||
}
|
||||
|
||||
fn parse_flags_definition(
|
||||
ctx: &ParserCtxBuilder<'a>,
|
||||
input: &'a str,
|
||||
) -> IResult<&'a str, (&'a str, PatternArg<'a>)> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, name) = identifier(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char('=')(input)?;
|
||||
let (input, flags) = Self::parse_flags(ctx, input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char(';')(input)?;
|
||||
|
||||
Ok((input, (name, flags)))
|
||||
}
|
||||
|
||||
fn parse_struct_definition(
|
||||
ctx: &ParserCtxBuilder<'a>,
|
||||
input: &'a str,
|
||||
) -> IResult<&'a str, (&'a str, PatternArg<'a>)> {
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = tag("struct")(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, name) = identifier(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char('=')(input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, struct_body) = Self::parse_struct(ctx, input)?;
|
||||
let (input, _) = multispace0(input)?;
|
||||
let (input, _) = char(';')(input)?;
|
||||
|
||||
Ok((input, (name, struct_body)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator that yields complete statements from SCML content.
|
||||
struct StatementIterator<'a> {
|
||||
lines: std::str::Lines<'a>,
|
||||
current_statement: String,
|
||||
}
|
||||
|
||||
impl<'a> StatementIterator<'a> {
|
||||
fn new(content: &'a str) -> Self {
|
||||
Self {
|
||||
lines: content.lines(),
|
||||
current_statement: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for StatementIterator<'_> {
|
||||
type Item = Result<&'static str, ScmlParseError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
match self.lines.next() {
|
||||
Some(line) => {
|
||||
let line = line.trim();
|
||||
// Skip comments and empty lines
|
||||
if line.starts_with("//") || line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.current_statement.push_str(line.trim_end());
|
||||
self.current_statement.push(' ');
|
||||
|
||||
// Check if statement is complete
|
||||
if self.current_statement.trim().ends_with(';') {
|
||||
let statement = self.current_statement.trim().to_string();
|
||||
self.current_statement.clear();
|
||||
// Leak the string to get a 'static lifetime reference
|
||||
let leaked: &'static str = Box::leak(statement.into_boxed_str());
|
||||
return Some(Ok(leaked));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// End of input
|
||||
if !self.current_statement.trim().is_empty() {
|
||||
let incomplete = self.current_statement.trim().to_string();
|
||||
self.current_statement.clear();
|
||||
return Some(Err(ScmlParseError::IncompleteStatement(incomplete)));
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,614 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{cell::RefCell, collections::HashMap, error::Error, fmt};
|
||||
|
||||
use nom::{
|
||||
IResult,
|
||||
branch::alt,
|
||||
bytes::complete::{tag, take_until, take_while1},
|
||||
character::complete::{char, digit1, space0, space1},
|
||||
combinator::{map, opt, peek, recognize, rest, value},
|
||||
multi::{separated_list0, separated_list1},
|
||||
sequence::{delimited, preceded, separated_pair, terminated, tuple},
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
/// Storage for blocked syscalls by PID in multi-threaded strace output.
|
||||
static BLOCKED_SYSCALL: RefCell<HashMap<u32, String>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum StraceParseError {
|
||||
BlockedLine,
|
||||
SignalLine,
|
||||
ExitLine,
|
||||
EmptyLine,
|
||||
ParseError { message: String, input: String },
|
||||
TypeError(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for StraceParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
StraceParseError::BlockedLine => write!(f, "Error: Blocked syscall line"),
|
||||
StraceParseError::SignalLine => write!(f, "Error: Signal line"),
|
||||
StraceParseError::ExitLine => write!(f, "Error: Exit status line"),
|
||||
StraceParseError::EmptyLine => write!(f, "Error: Empty line"),
|
||||
StraceParseError::ParseError { message, input } => {
|
||||
write!(f, "{} (input: {})", message, input)
|
||||
}
|
||||
StraceParseError::TypeError(msg) => write!(f, "Type error: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for StraceParseError {}
|
||||
|
||||
/// Syscall representation parsed from strace output.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) struct Syscall<'a> {
|
||||
pid: u32,
|
||||
name: &'a str,
|
||||
args: Vec<SyscallArg<'a>>,
|
||||
return_value: &'a str,
|
||||
original_line: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Syscall<'a> {
|
||||
/// Fetches and preprocesses a strace line before parsing.
|
||||
pub(crate) fn fetch(line: String) -> Result<String, StraceParseError> {
|
||||
let trimmed = line.as_str().trim();
|
||||
|
||||
if trimmed.is_empty() {
|
||||
return Err(StraceParseError::EmptyLine);
|
||||
}
|
||||
|
||||
// Skip signal lines
|
||||
if Self::parse_signal_line(trimmed).is_ok() {
|
||||
return Err(StraceParseError::SignalLine);
|
||||
}
|
||||
|
||||
// Skip exit status lines
|
||||
if Self::parse_exit_line(trimmed).is_ok() {
|
||||
return Err(StraceParseError::ExitLine);
|
||||
}
|
||||
|
||||
// Save blocked syscalls for later reconstruction
|
||||
if let Ok((_, (pid, str))) = Self::parse_multithread_blocked(trimmed) {
|
||||
BLOCKED_SYSCALL.with(|blocked| {
|
||||
blocked.borrow_mut().insert(pid, str);
|
||||
});
|
||||
return Err(StraceParseError::BlockedLine);
|
||||
}
|
||||
|
||||
if let Ok((_, (pid, resumed))) = Self::parse_multithread_resumed(trimmed) {
|
||||
let blocked_call =
|
||||
BLOCKED_SYSCALL.with(|blocked| blocked.borrow().get(&pid).cloned().unwrap());
|
||||
let reconstructed = format!("{} {}{}", pid, blocked_call, resumed);
|
||||
return Ok(reconstructed);
|
||||
}
|
||||
|
||||
Ok(line)
|
||||
}
|
||||
|
||||
/// Parses a single line of strace output into a Syscall.
|
||||
pub(crate) fn parse(input: &'a str) -> Result<Self, StraceParseError> {
|
||||
let trimmed = input.trim();
|
||||
|
||||
let syscall = Self::parse_syscall(trimmed)
|
||||
.map(|(_, syscall)| syscall)
|
||||
.map_err(|e| StraceParseError::ParseError {
|
||||
message: e.to_string(),
|
||||
input: trimmed.to_string(),
|
||||
})?;
|
||||
|
||||
let syscall = Self::handle_special_cases(syscall);
|
||||
Ok(syscall)
|
||||
}
|
||||
|
||||
pub(crate) fn name(&self) -> &str {
|
||||
self.name
|
||||
}
|
||||
|
||||
pub(crate) fn original_line(&self) -> &str {
|
||||
self.original_line
|
||||
}
|
||||
|
||||
pub(crate) fn args(&self) -> &[SyscallArg<'_>] {
|
||||
&self.args
|
||||
}
|
||||
|
||||
fn new(
|
||||
pid: u32,
|
||||
name: &'a str,
|
||||
args: Vec<SyscallArg<'a>>,
|
||||
return_value: &'a str,
|
||||
original_line: &'a str,
|
||||
) -> Self {
|
||||
Self {
|
||||
pid,
|
||||
name,
|
||||
args,
|
||||
return_value,
|
||||
original_line,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) enum SyscallArg<'a> {
|
||||
/// Integer argument represented as a string.
|
||||
Integer(&'a str),
|
||||
|
||||
/// Quoted string argument.
|
||||
String(&'a str),
|
||||
|
||||
/// Unquoted flag argument.
|
||||
Flag(&'a str),
|
||||
|
||||
/// File descriptor with absolute path.
|
||||
FdPath(&'a str),
|
||||
|
||||
/// Combination of flags and/or integer values.
|
||||
Flags(SyscallFlagSet<'a>),
|
||||
|
||||
/// Structured data represented as key-value pairs.
|
||||
Struct(SyscallStruct<'a>),
|
||||
|
||||
/// Array of syscall arguments.
|
||||
Array(SyscallArray<'a>),
|
||||
|
||||
/// Argument is ignored.
|
||||
Ignored,
|
||||
}
|
||||
|
||||
/// Wrapper for arrays of syscall arguments.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) struct SyscallArray<'a>(Vec<SyscallArg<'a>>);
|
||||
|
||||
impl<'a> SyscallArray<'a> {
|
||||
pub(crate) fn elements(&self) -> &[SyscallArg<'a>] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn new(elements: Vec<SyscallArg<'a>>) -> Result<Self, StraceParseError> {
|
||||
Ok(Self(elements))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for flag sets with restricted element types.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) struct SyscallFlagSet<'a>(Vec<SyscallArg<'a>>);
|
||||
|
||||
impl<'a> SyscallFlagSet<'a> {
|
||||
pub(crate) fn flags(&self) -> &[SyscallArg<'_>] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn new(flags: Vec<SyscallArg<'a>>) -> Result<Self, StraceParseError> {
|
||||
// Validates that all elements are either `Flag` or `Integer` types,
|
||||
// as these are the only valid types for flag combinations.
|
||||
for flag in &flags {
|
||||
match flag {
|
||||
SyscallArg::Flag(_) | SyscallArg::Integer(_) => {}
|
||||
_ => {
|
||||
return Err(StraceParseError::TypeError(
|
||||
"SyscallFlagSet elements can only be Flag or Integer types".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self(flags))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for structured data represented as key-value pairs.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub(crate) struct SyscallStruct<'a>(HashMap<&'a str, SyscallArg<'a>>);
|
||||
|
||||
impl<'a> SyscallStruct<'a> {
|
||||
pub(crate) fn fields(&self) -> &HashMap<&'a str, SyscallArg<'a>> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub(crate) fn get_value(&self, key: &str) -> Option<&SyscallArg<'_>> {
|
||||
self.0.get(key)
|
||||
}
|
||||
|
||||
fn new(fields: HashMap<&'a str, SyscallArg<'a>>) -> Self {
|
||||
Self(fields)
|
||||
}
|
||||
}
|
||||
|
||||
impl Syscall<'_> {
|
||||
fn parse_syscall(input: &str) -> IResult<&str, Syscall<'_>> {
|
||||
let original_input = input;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, pid) = opt(terminated(Self::parse_pid, space1))(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, name) = Self::parse_name(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, args) = Self::parse_args(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, return_value) = Self::parse_return_value(input)?;
|
||||
|
||||
Ok((
|
||||
input,
|
||||
Syscall::new(pid.unwrap_or(0), name, args, return_value, original_input),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_name(input: &str) -> IResult<&str, &str> {
|
||||
delimited(
|
||||
space0,
|
||||
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'),
|
||||
space0,
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn parse_args(input: &str) -> IResult<&str, Vec<SyscallArg<'_>>> {
|
||||
delimited(
|
||||
char('('),
|
||||
separated_list0(char(','), delimited(space0, Self::parse_arg, space0)),
|
||||
char(')'),
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn parse_arg(input: &str) -> IResult<&str, SyscallArg<'_>> {
|
||||
// Skip parameter's name
|
||||
let (input, _) = opt(terminated(
|
||||
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'),
|
||||
char('='),
|
||||
))(input)?;
|
||||
|
||||
let (input, arg) = alt((
|
||||
Self::parse_none,
|
||||
Self::parse_fd_path,
|
||||
Self::parse_struct,
|
||||
Self::parse_array,
|
||||
Self::parse_quoted_string,
|
||||
Self::parse_mask,
|
||||
Self::parse_hex,
|
||||
Self::parse_number,
|
||||
Self::parse_flags,
|
||||
))(input)?;
|
||||
|
||||
// Skip comment
|
||||
let (input, _) = opt(delimited(
|
||||
delimited(space0, tag("/*"), space0),
|
||||
take_until("*/"),
|
||||
tag("*/"),
|
||||
))(input)?;
|
||||
|
||||
// Skip output parameter with arrow
|
||||
let (input, _) = opt(preceded(
|
||||
delimited(space0, tag("=>"), space0),
|
||||
Self::parse_arg,
|
||||
))(input)?;
|
||||
|
||||
Ok((input, arg))
|
||||
}
|
||||
|
||||
/// Parses a file descriptor or `AT_FDCWD` with absolute path argument.
|
||||
fn parse_fd_path(input: &str) -> IResult<&str, SyscallArg<'_>> {
|
||||
let (input, _fd) =
|
||||
alt((tag("AT_FDCWD"), take_while1(|c: char| c.is_ascii_digit())))(input)?;
|
||||
let (input, path) = Self::parse_angle_bracket_content(input)?;
|
||||
|
||||
Ok((input, SyscallArg::FdPath(path)))
|
||||
}
|
||||
|
||||
/// Parses content within angle brackets, handling nested brackets and arrows.
|
||||
fn parse_angle_bracket_content(input: &str) -> IResult<&str, &str> {
|
||||
let (input, _) = char::<&str, nom::error::Error<&str>>('<')(input)?;
|
||||
|
||||
let mut depth = 1;
|
||||
let mut end_pos = 0;
|
||||
let chars = input.char_indices().peekable();
|
||||
|
||||
for (i, ch) in chars {
|
||||
match ch {
|
||||
'<' => depth += 1,
|
||||
'>' => {
|
||||
// Check if this is part of '->' arrow
|
||||
// Look back to see if previous char was '-'
|
||||
if i > 0 && input.as_bytes().get(i - 1) == Some(&b'-') {
|
||||
// This '>' is part of '->', not a closing bracket
|
||||
continue;
|
||||
}
|
||||
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
end_pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if depth != 0 {
|
||||
return Err(nom::Err::Error(nom::error::Error::new(
|
||||
input,
|
||||
nom::error::ErrorKind::Tag,
|
||||
)));
|
||||
}
|
||||
|
||||
let content = &input[..end_pos];
|
||||
let remaining = &input[end_pos + 1..];
|
||||
Ok((remaining, content))
|
||||
}
|
||||
|
||||
/// Parses a quoted string argument.
|
||||
fn parse_quoted_string(input: &str) -> IResult<&str, SyscallArg<'_>> {
|
||||
map(
|
||||
tuple((
|
||||
opt(char('@')), // Optional @ prefix
|
||||
delimited(char('"'), Self::take_until_unescaped_quote, char('"')),
|
||||
opt(tag("...")), // Optional ... suffix
|
||||
)),
|
||||
|(_, content, _)| SyscallArg::String(content),
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// Helper to take characters until an unescaped quote is found.
|
||||
fn take_until_unescaped_quote(input: &str) -> IResult<&str, &str> {
|
||||
let chars = input.char_indices();
|
||||
let mut last_was_escape = false;
|
||||
|
||||
for (i, ch) in chars {
|
||||
if ch == '"' && !last_was_escape {
|
||||
return Ok((&input[i..], &input[..i]));
|
||||
}
|
||||
last_was_escape = ch == '\\' && !last_was_escape;
|
||||
}
|
||||
|
||||
Err(nom::Err::Error(nom::error::Error::new(
|
||||
input,
|
||||
nom::error::ErrorKind::Tag,
|
||||
)))
|
||||
}
|
||||
|
||||
fn parse_hex(input: &str) -> IResult<&str, SyscallArg<'_>> {
|
||||
map(
|
||||
recognize(preceded(
|
||||
tag("0x"),
|
||||
take_while1(|c: char| c.is_ascii_hexdigit()),
|
||||
)),
|
||||
|s: &str| SyscallArg::Integer(s),
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn parse_number(input: &str) -> IResult<&str, SyscallArg<'_>> {
|
||||
// Supports negative numbers and arithmetic operations like left shift
|
||||
// and multiplication.
|
||||
//
|
||||
// First, check if this looks like `number<<FLAG` pattern
|
||||
// If so, fail early to let other parsers handle it
|
||||
if let Ok((remaining, _)) =
|
||||
recognize::<_, _, nom::error::Error<&str>, _>(tuple((digit1, tag("<<"))))(input)
|
||||
{
|
||||
// Check if what follows is an identifier (not a digit)
|
||||
if peek(take_while1::<_, _, nom::error::Error<&str>>(|c: char| {
|
||||
c.is_ascii_alphabetic() || c == '_'
|
||||
}))(remaining)
|
||||
.is_ok()
|
||||
{
|
||||
// This is `number<<FLAG` format, reject it
|
||||
return Err(nom::Err::Error(nom::error::Error::new(
|
||||
input,
|
||||
nom::error::ErrorKind::Verify,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
map(
|
||||
recognize(tuple((
|
||||
opt(char('-')),
|
||||
digit1,
|
||||
opt(tuple((alt((tag("<<"), tag("*"))), opt(char('-')), digit1))),
|
||||
))),
|
||||
|s: &str| SyscallArg::Integer(s),
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn parse_flags(input: &str) -> IResult<&str, SyscallArg<'_>> {
|
||||
// Flags can be separated by `|` or ` or `
|
||||
map(
|
||||
separated_list1(
|
||||
alt((tag(" or "), tag("|"))),
|
||||
alt((
|
||||
Self::parse_hex,
|
||||
Self::parse_number,
|
||||
Self::parse_unquoted_flag,
|
||||
)),
|
||||
),
|
||||
|flags| SyscallArg::Flags(SyscallFlagSet::new(flags).unwrap()),
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn parse_unquoted_flag(input: &str) -> IResult<&str, SyscallArg<'_>> {
|
||||
// Flags are symbolic constants that may optionally include:
|
||||
// - Parenthetical parameters: `FLAG(param)`
|
||||
// - Left shift operations: `FLAG<<2` and `1<<FLAG`
|
||||
alt((
|
||||
map(
|
||||
recognize(tuple((
|
||||
take_while1(|c: char| c.is_ascii_digit()),
|
||||
tag("<<"),
|
||||
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_' || c == '?'),
|
||||
))),
|
||||
|matched: &str| SyscallArg::Flag(matched),
|
||||
),
|
||||
map(
|
||||
recognize(tuple((
|
||||
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_' || c == '?'),
|
||||
opt(delimited(
|
||||
char::<&str, nom::error::Error<&str>>('('),
|
||||
take_until(")"),
|
||||
char::<&str, nom::error::Error<&str>>(')'),
|
||||
)),
|
||||
opt(tuple((
|
||||
tag("<<"),
|
||||
take_while1(|c: char| c.is_ascii_digit()),
|
||||
))),
|
||||
))),
|
||||
|matched: &str| SyscallArg::Flag(matched),
|
||||
),
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn parse_mask(input: &str) -> IResult<&str, SyscallArg<'_>> {
|
||||
// Parse the format like `~[flags]`
|
||||
value(
|
||||
SyscallArg::Ignored,
|
||||
delimited(tag("~["), take_until("]"), char(']')),
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn parse_struct(input: &str) -> IResult<&str, SyscallArg<'_>> {
|
||||
let (input, _) = char('{')(input)?;
|
||||
let (input, pairs) = separated_list0(
|
||||
char(','),
|
||||
preceded(
|
||||
space0,
|
||||
alt((
|
||||
value(None, tag("...")),
|
||||
map(
|
||||
separated_pair(
|
||||
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '_'),
|
||||
char('='),
|
||||
Self::parse_arg,
|
||||
),
|
||||
|(k, v)| Some((k, v)),
|
||||
),
|
||||
)),
|
||||
),
|
||||
)(input)?;
|
||||
let (input, _) = char('}')(input)?;
|
||||
|
||||
let fields: HashMap<&str, SyscallArg> = pairs.into_iter().flatten().collect();
|
||||
let result = if fields.is_empty() {
|
||||
SyscallArg::Ignored
|
||||
} else {
|
||||
SyscallArg::Struct(SyscallStruct::new(fields))
|
||||
};
|
||||
|
||||
Ok((input, result))
|
||||
}
|
||||
|
||||
fn parse_array(input: &str) -> IResult<&str, SyscallArg<'_>> {
|
||||
alt((
|
||||
|input| {
|
||||
let (remaining, elements) = delimited(
|
||||
char('['),
|
||||
separated_list0(char(','), preceded(space0, Self::parse_arg)),
|
||||
char(']'),
|
||||
)(input)?;
|
||||
|
||||
match SyscallArray::new(elements) {
|
||||
Ok(array) => Ok((remaining, SyscallArg::Array(array))),
|
||||
Err(_) => Err(nom::Err::Error(nom::error::Error::new(
|
||||
input,
|
||||
nom::error::ErrorKind::Verify,
|
||||
))),
|
||||
}
|
||||
},
|
||||
value(
|
||||
SyscallArg::Ignored,
|
||||
delimited(char('['), take_until("]"), char(']')),
|
||||
),
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn parse_return_value(input: &str) -> IResult<&str, &str> {
|
||||
preceded(
|
||||
tuple((space0, char('='), space0)),
|
||||
map(rest, |s: &str| s.trim()),
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn parse_pid(input: &str) -> IResult<&str, u32> {
|
||||
map(digit1, |s: &str| s.parse::<u32>().unwrap())(input)
|
||||
}
|
||||
|
||||
fn parse_none(input: &str) -> IResult<&str, SyscallArg<'_>> {
|
||||
// Parse the format like:
|
||||
// func(arg1, , arg3)
|
||||
// func(arg1,)
|
||||
value(SyscallArg::Ignored, recognize(peek(char(','))))(input)
|
||||
}
|
||||
|
||||
fn parse_signal_line(input: &str) -> IResult<&str, ()> {
|
||||
// Parse the format like:
|
||||
// --- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=123, si_uid=1000} ---
|
||||
value(
|
||||
(),
|
||||
tuple((
|
||||
opt(terminated(Self::parse_pid, space1)),
|
||||
delimited(tag("---"), take_until("---"), tag("---")),
|
||||
)),
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn parse_exit_line(input: &str) -> IResult<&str, ()> {
|
||||
// Parse the format like:
|
||||
// +++ exited with 0 +++
|
||||
value(
|
||||
(),
|
||||
tuple((
|
||||
opt(terminated(Self::parse_pid, space1)),
|
||||
delimited(tag("+++"), take_until("+++"), tag("+++")),
|
||||
)),
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn parse_multithread_blocked(input: &str) -> IResult<&str, (u32, String)> {
|
||||
// Parse the format like:
|
||||
// 123 read(3</path/to/file>, <unfinished ...>
|
||||
map(
|
||||
tuple((
|
||||
Self::parse_pid,
|
||||
preceded(
|
||||
space1,
|
||||
terminated(take_until("<unfinished ...>"), tag("<unfinished ...>")),
|
||||
),
|
||||
)),
|
||||
|(pid, content)| (pid, content.trim().to_string()),
|
||||
)(input)
|
||||
}
|
||||
|
||||
fn parse_multithread_resumed(input: &str) -> IResult<&str, (u32, String)> {
|
||||
// Parse the format like:
|
||||
// 123 <... read resumed> "\x00\x01\x02", 1024) = 3
|
||||
map(
|
||||
tuple((
|
||||
Self::parse_pid,
|
||||
preceded(
|
||||
space1,
|
||||
tuple((
|
||||
delimited(tag("<..."), take_until("resumed>"), tag("resumed>")),
|
||||
rest,
|
||||
)),
|
||||
),
|
||||
)),
|
||||
|(pid, (_, remaining))| (pid, remaining.trim().to_string()),
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// Handles special cases for certain syscalls whose strace output is non-standard.
|
||||
fn handle_special_cases(mut syscall: Syscall) -> Syscall {
|
||||
match syscall.name {
|
||||
// For `clone`, strace removes the first and fourth arguments, just insert
|
||||
// ignored args.
|
||||
"clone" => {
|
||||
syscall.args.insert(0, SyscallArg::Ignored);
|
||||
syscall.args.insert(3, SyscallArg::Ignored);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
syscall
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt,
|
||||
fs::File,
|
||||
io::{BufRead, BufReader, Lines, Read},
|
||||
os::unix::io::FromRawFd,
|
||||
path::Path,
|
||||
process::Command,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TraceError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for TraceError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TraceError> for String {
|
||||
fn from(err: TraceError) -> Self {
|
||||
err.message
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for TraceError {}
|
||||
|
||||
impl TraceError {
|
||||
fn new(message: &str) -> Self {
|
||||
Self {
|
||||
message: message.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A stream of strace log entries.
|
||||
pub struct StraceLogStream(Box<dyn BufRead>);
|
||||
|
||||
impl Read for StraceLogStream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
self.0.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufRead for StraceLogStream {
|
||||
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
|
||||
self.0.fill_buf()
|
||||
}
|
||||
|
||||
fn consume(&mut self, amt: usize) {
|
||||
self.0.consume(amt)
|
||||
}
|
||||
}
|
||||
|
||||
impl StraceLogStream {
|
||||
/// Creates a new stream by opening an existing strace log file.
|
||||
pub fn open_file<P: AsRef<Path>>(path: P) -> Result<Self, TraceError> {
|
||||
let file = File::open(path.as_ref())
|
||||
.map_err(|e| TraceError::new(&format!("Failed to open log file: {}", e)))?;
|
||||
Ok(Self(Box::new(BufReader::new(file))))
|
||||
}
|
||||
|
||||
/// Creates a new stream by running a new command with strace.
|
||||
pub fn run_cmd<P: AsRef<Path>>(path: P, args: Vec<&str>) -> Result<Self, TraceError> {
|
||||
let mut command_str = path.as_ref().to_string_lossy().to_string();
|
||||
for arg in args {
|
||||
command_str.push(' ');
|
||||
command_str.push_str(arg);
|
||||
}
|
||||
|
||||
// Create pipe
|
||||
let (read_fd, write_fd) = nix::unistd::pipe()
|
||||
.map_err(|e| TraceError::new(&format!("Failed to create pipe: {}", e)))?;
|
||||
|
||||
// Convert read end to File
|
||||
let read_file = unsafe { std::fs::File::from_raw_fd(read_fd) };
|
||||
|
||||
// Start strace, using /proc/self/fd/N to access the write end
|
||||
Command::new("strace")
|
||||
.args([
|
||||
"-o",
|
||||
&format!("/proc/self/fd/{}", write_fd),
|
||||
"-yy",
|
||||
"-f",
|
||||
"sh",
|
||||
"-c",
|
||||
&command_str,
|
||||
])
|
||||
.spawn()
|
||||
.map_err(|e| {
|
||||
// Clean up file descriptors
|
||||
nix::unistd::close(read_fd).ok();
|
||||
nix::unistd::close(write_fd).ok();
|
||||
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
TraceError::new(
|
||||
"strace command not found. Please install strace:\n\
|
||||
- Debian/Ubuntu: sudo apt-get install strace\n\
|
||||
- Fedora/RHEL: sudo dnf install strace",
|
||||
)
|
||||
} else {
|
||||
TraceError::new(&format!(
|
||||
"Failed to start strace: {}\n\
|
||||
If this is a permission error, try:\n\
|
||||
sudo sctrace <pattern_file> -- {}",
|
||||
e, command_str
|
||||
))
|
||||
}
|
||||
})?;
|
||||
|
||||
// Close write end (strace has already inherited it)
|
||||
nix::unistd::close(write_fd).ok();
|
||||
|
||||
Ok(Self(Box::new(BufReader::new(read_file))))
|
||||
}
|
||||
|
||||
/// Creates a new stream by a string of strace log.
|
||||
pub fn from_string(log_str: &str) -> Result<Self, TraceError> {
|
||||
let cursor = std::io::Cursor::new(log_str.to_string().into_bytes());
|
||||
Ok(Self(Box::new(BufReader::new(cursor))))
|
||||
}
|
||||
|
||||
/// Returns an iterator over the lines of this stream.
|
||||
pub fn lines(self) -> Lines<Self> {
|
||||
BufRead::lines(self)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use sctrace::{CliReporterBuilder, Patterns, SctraceBuilder, StraceLogStream};
|
||||
|
||||
#[test]
|
||||
fn test_open_syscall() {
|
||||
let scml_content = r#"
|
||||
access_mode =
|
||||
O_RDONLY |
|
||||
O_WRONLY |
|
||||
O_RDWR;
|
||||
creation_flags =
|
||||
O_CLOEXEC |
|
||||
O_DIRECTORY |
|
||||
O_EXCL |
|
||||
O_NOCTTY |
|
||||
O_NOFOLLOW |
|
||||
O_TRUNC;
|
||||
status_flags =
|
||||
O_APPEND |
|
||||
O_ASYNC |
|
||||
O_DIRECT |
|
||||
O_LARGEFILE |
|
||||
O_NOATIME |
|
||||
O_NONBLOCK |
|
||||
O_SYNC;
|
||||
|
||||
// Open an existing file
|
||||
open(
|
||||
path,
|
||||
flags = <access_mode> | <creation_flags> | <status_flags>,
|
||||
);
|
||||
openat(
|
||||
dirfd,
|
||||
path,
|
||||
flags = <access_mode> | <creation_flags> | <status_flags>,
|
||||
);
|
||||
|
||||
// Create a new file
|
||||
open(
|
||||
path,
|
||||
flags = O_CREAT | <access_mode> | <creation_flags> | <status_flags>,
|
||||
mode
|
||||
);
|
||||
openat(
|
||||
dirfd,
|
||||
path,
|
||||
flags = O_CREAT | <access_mode> | <creation_flags> | <status_flags>,
|
||||
mode
|
||||
);
|
||||
|
||||
// Status flags that are meaningful with O_PATH
|
||||
opath_valid_flags = O_CLOEXEC | O_DIRECTORY | O_NOFOLLOW;
|
||||
// All other flags are ignored with O_PATH
|
||||
opath_ignored_flags = O_CREAT | <creation_flags> | <status_flags>;
|
||||
// Obtain a file descriptor to indicate a location in FS
|
||||
open(
|
||||
path,
|
||||
flags = O_PATH | <opath_valid_flags> | <opath_ignored_flags>
|
||||
);
|
||||
openat(
|
||||
dirfd,
|
||||
path,
|
||||
flags = O_PATH | <opath_valid_flags> | <opath_ignored_flags>
|
||||
);
|
||||
|
||||
// Create an unnamed file
|
||||
// open(path, flags = O_TMPFILE | <creation_flags> | <status_flags>)
|
||||
"#;
|
||||
|
||||
let log_lines = r#"
|
||||
openat(AT_FDCWD, "/lib/aarch64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
|
||||
open("/dev/tdx_guest", O_RDWR|O_NONBLOCK) = 3
|
||||
open("/tmp/sctrace_testfile", O_CREAT|O_RDWR|O_CLOEXEC, 0666) = 4
|
||||
openat(AT_FDCWD, "/tmp/sctrace_testfile2", O_PATH|O_CREAT) = 5
|
||||
"#;
|
||||
|
||||
let sctrace = SctraceBuilder::new()
|
||||
.patterns(Patterns::from_scml(scml_content).unwrap())
|
||||
.strace(StraceLogStream::from_string(log_lines).unwrap())
|
||||
.reporter(CliReporterBuilder::new().quiet().collect().build())
|
||||
.build();
|
||||
|
||||
let result = sctrace.run().unwrap().unwrap();
|
||||
assert_eq!(result.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timer_create_syscall() {
|
||||
let scml_content = r#"
|
||||
opt_notify_methods = SIGEV_NONE | SIGEV_SIGNAL | SIGEV_THREAD_ID;
|
||||
|
||||
// Create a timer with predefined clock source
|
||||
timer_create(
|
||||
clockid = CLOCK_PROCESS_CPUTIME_ID | CLOCK_THREAD_CPUTIME_ID | CLOCK_REALTIME | CLOCK_MONOTONIC | CLOCK_BOOTTIME,
|
||||
sevp = {
|
||||
sigev_notify = <opt_notify_methods>,
|
||||
..
|
||||
},
|
||||
timerid
|
||||
);
|
||||
|
||||
// Create a timer based on a per-process or per-thread clock
|
||||
timer_create(
|
||||
clockid = <INTEGER>,
|
||||
sevp = {
|
||||
sigev_notify = <opt_notify_methods>,
|
||||
..
|
||||
},
|
||||
timerid
|
||||
);
|
||||
"#;
|
||||
|
||||
let log_lines = r#"
|
||||
timer_create(CLOCK_REALTIME, {sigev_notify=SIGEV_SIGNAL, sigev_signo=SIGALRM, sigev_value={sival_ptr=0x559b4d3e2e70}}, 0x7ffcb1f4d9c0) = 0
|
||||
timer_create(0xff5be79e /* CLOCK_??? */, {sigev_value={sival_int=565425088, sival_ptr=0x562221b3b3c0}, sigev_signo=SIGRTMIN, sigev_notify=SIGEV_THREAD_ID, sigev_notify_thread_id=1344269}, [0]) = 0
|
||||
"#;
|
||||
|
||||
let sctrace = SctraceBuilder::new()
|
||||
.patterns(Patterns::from_scml(scml_content).unwrap())
|
||||
.strace(StraceLogStream::from_string(log_lines).unwrap())
|
||||
.reporter(CliReporterBuilder::new().quiet().collect().build())
|
||||
.build();
|
||||
|
||||
let result = sctrace.run().unwrap().unwrap();
|
||||
assert_eq!(result.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_struct_with_same_name() {
|
||||
let scml_content = r#"
|
||||
struct cmsghdr = {
|
||||
cmsg_level = SOL_SOCKET,
|
||||
cmsg_type = SO_TIMESTAMP_OLD | SCM_RIGHTS | SCM_CREDENTIALS,
|
||||
..
|
||||
};
|
||||
struct cmsghdr = {
|
||||
cmsg_level = SOL_IP,
|
||||
cmsg_type = IP_TTL,
|
||||
..
|
||||
};
|
||||
|
||||
// Rule for message header, which refers to the rules for control message header
|
||||
struct msghdr = {
|
||||
msg_control = [ <cmsghdr> ],
|
||||
..
|
||||
};
|
||||
|
||||
recvmsg(socket, message = <msghdr>, flags);
|
||||
"#;
|
||||
|
||||
let log_lines = &[
|
||||
"recvmsg(4, {msg_name=NULL, msg_namelen=0, msg_iov=NULL, msg_iovlen=0, msg_control=[{cmsg_len=16, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS}], msg_controllen=16, msg_flags=0}, 0) = 24",
|
||||
"recvmsg(5, {msg_name=NULL, msg_namelen=0, msg_iov=NULL, msg_iovlen=0, msg_control=[{cmsg_len=16, cmsg_level=SOL_IP, cmsg_type=IP_TTL}], msg_controllen=16, msg_flags=0}, 0) = 24",
|
||||
"recvmsg(6, {msg_name=NULL, msg_namelen=0, msg_iov=NULL, msg_iovlen=0, msg_control=[{cmsg_len=16, cmsg_level=SOL_IPV6, cmsg_type=IPV6_UNICAST_HOPS}], msg_controllen=16, msg_flags=0}, 0) = 24",
|
||||
];
|
||||
|
||||
let sctrace = SctraceBuilder::new()
|
||||
.patterns(Patterns::from_scml(scml_content).unwrap())
|
||||
.strace(StraceLogStream::from_string(log_lines.join("\n").as_str()).unwrap())
|
||||
.reporter(CliReporterBuilder::new().quiet().collect().build())
|
||||
.build();
|
||||
|
||||
let result = sctrace.run().unwrap().unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
assert_eq!(result[0], format!("Unsupported syscall: {}", log_lines[2]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clone_syscall() {
|
||||
let scml_content = r#"
|
||||
signal_flags = SIGHUP | SIGINT | SIGQUIT | SIGILL |
|
||||
SIGTRAP | SIGABRT | SIGSTKFLT | SIGFPE |
|
||||
SIGKILL | SIGBUS | SIGSEGV | SIGXCPU |
|
||||
SIGPIPE | SIGALRM | SIGTERM | SIGUSR1 |
|
||||
SIGUSR2 | SIGCHLD | SIGPWR | SIGVTALRM |
|
||||
SIGPROF | SIGIO | SIGWINCH | SIGSTOP |
|
||||
SIGTSTP | SIGCONT | SIGTTIN | SIGTTOU |
|
||||
SIGURG | SIGXFSZ | SIGSYS | SIGRTMIN;
|
||||
|
||||
opt_flags =
|
||||
// Optional flags
|
||||
//
|
||||
// Share the parent's virtual memory
|
||||
CLONE_VM |
|
||||
// Share the parent's filesystem
|
||||
CLONE_FS |
|
||||
// Share the parent's file descriptor table
|
||||
CLONE_FILES |
|
||||
// Share the parent's signal handlers
|
||||
CLONE_SIGHAND |
|
||||
// Place child in the same thread group as parent
|
||||
CLONE_THREAD |
|
||||
// Share the parent's System V semaphore adjustments
|
||||
CLONE_SYSVSEM |
|
||||
// Suspend parent until the child exits or calls `execve`
|
||||
CLONE_VFORK |
|
||||
// Create a new mount namespace for the child
|
||||
CLONE_NEWNS |
|
||||
// Write child `TID` to parent's memory
|
||||
CLONE_PARENT_SETTID |
|
||||
// Allocate a `PID` file descriptor for the child
|
||||
CLONE_PIDFD |
|
||||
// Set thread-local storage for the child
|
||||
CLONE_SETTLS |
|
||||
// Write child `TID` to child's memory
|
||||
CLONE_CHILD_SETTID |
|
||||
// Clear child `TID` in child's memory on exit
|
||||
CLONE_CHILD_CLEARTID |
|
||||
// Make the child's parent the same as the caller's parent
|
||||
CLONE_PARENT;
|
||||
|
||||
// Create a thread or process
|
||||
clone(
|
||||
fn, stack,
|
||||
flags = <opt_flags> | <signal_flags>,
|
||||
func_arg, ..
|
||||
);
|
||||
"#;
|
||||
|
||||
let log_lines = r#"
|
||||
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7745c1ca10) = 141614
|
||||
"#;
|
||||
|
||||
let sctrace = SctraceBuilder::new()
|
||||
.patterns(Patterns::from_scml(scml_content).unwrap())
|
||||
.strace(StraceLogStream::from_string(log_lines).unwrap())
|
||||
.reporter(CliReporterBuilder::new().quiet().collect().build())
|
||||
.build();
|
||||
|
||||
let result = sctrace.run().unwrap().unwrap();
|
||||
assert_eq!(result.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_threads_syscalls() {
|
||||
let scml_content = r#"
|
||||
wait4(
|
||||
pid, wstatus,
|
||||
options = WNOHANG | WSTOPPED | WCONTINUED | WNOWAIT,
|
||||
rusage
|
||||
);
|
||||
"#;
|
||||
|
||||
let log_lines = r#"
|
||||
141611 wait4(-1, <unfinished ...>
|
||||
141611 <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG, NULL) = 141612
|
||||
"#;
|
||||
|
||||
let sctrace = SctraceBuilder::new()
|
||||
.patterns(Patterns::from_scml(scml_content).unwrap())
|
||||
.strace(StraceLogStream::from_string(log_lines).unwrap())
|
||||
.reporter(CliReporterBuilder::new().quiet().collect().build())
|
||||
.build();
|
||||
|
||||
let result = sctrace.run().unwrap().unwrap();
|
||||
assert_eq!(result.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_logfile_wildcard_pattern() {
|
||||
let scml_content = r#"
|
||||
openat(dirfd, pathname, flags, ..);
|
||||
"#;
|
||||
|
||||
let log_lines = r#"
|
||||
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
|
||||
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC, 0755) = 4
|
||||
"#;
|
||||
|
||||
let sctrace = SctraceBuilder::new()
|
||||
.patterns(Patterns::from_scml(scml_content).unwrap())
|
||||
.strace(StraceLogStream::from_string(log_lines).unwrap())
|
||||
.reporter(CliReporterBuilder::new().quiet().collect().build())
|
||||
.build();
|
||||
|
||||
let result = sctrace.run().unwrap().unwrap();
|
||||
assert_eq!(result.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_program_simple_command() {
|
||||
let scml_content = r#"
|
||||
execve(filename, argv, envp);
|
||||
"#;
|
||||
|
||||
let sctrace = SctraceBuilder::new()
|
||||
.patterns(Patterns::from_scml(scml_content).unwrap())
|
||||
.strace(StraceLogStream::run_cmd("/bin/true", vec![]).unwrap())
|
||||
.reporter(CliReporterBuilder::new().quiet().collect().build())
|
||||
.build();
|
||||
|
||||
let result = sctrace.run().unwrap().unwrap();
|
||||
assert!(result.iter().all(|error| !error.contains("execve(")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_heterogeneous_arrays() {
|
||||
let scml_content = r#"
|
||||
struct iovec = {
|
||||
iov_base = [
|
||||
[
|
||||
{
|
||||
nlmsg_type = RTM_NEWADDR,
|
||||
..
|
||||
},
|
||||
[
|
||||
[ { nla_type = IFA_CACHEINFO, .. } ]
|
||||
]
|
||||
]
|
||||
],
|
||||
..
|
||||
};
|
||||
recvmsg(
|
||||
sockfd,
|
||||
msg = {
|
||||
msg_iov = [ <iovec> ],
|
||||
..
|
||||
},
|
||||
flags
|
||||
);
|
||||
"#;
|
||||
|
||||
let log_lines = r#"
|
||||
1370921 recvmsg(7<socket:[181857552]>, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=0, msg_iov=[{iov_base=[[{nlmsg_len=76, nlmsg_type=RTM_NEWADDR, nlmsg_flags=NLM_F_MULTI, nlmsg_seq=1758615457, nlmsg_pid=1370920}, [[{nla_len=20, nla_type=IFA_CACHEINFO}]]]], iov_len=4096}], msg_iovlen=1, msg_control=NULL, msg_controllen=0, msg_flags=0}, 0) = 1280
|
||||
"#;
|
||||
|
||||
let sctrace = SctraceBuilder::new()
|
||||
.patterns(Patterns::from_scml(scml_content).unwrap())
|
||||
.strace(StraceLogStream::from_string(log_lines).unwrap())
|
||||
.reporter(CliReporterBuilder::new().quiet().collect().build())
|
||||
.build();
|
||||
|
||||
let result = sctrace.run().unwrap().unwrap();
|
||||
assert_eq!(result.len(), 0);
|
||||
}
|
||||
Loading…
Reference in New Issue