Skip to content

Conversation

@mmarchini
Copy link
Contributor

This is a first step towards making llnode more reliable. Right now,
llnode doesn't allow us to deal with partially-loaded objects (some
fields working, some not working). As a postmortem tool, llnode should
be able to handle partial loading of objects, as this is the proper way
to deal with corrupt memory. If llnode can handle corrupted memory, it
will also be able to understand the memory of unsupported Node.js
versons, although some fields and objects won't load.

There are two problems regarding reliability in llnode: first, we have
several attempts to access fields from the crashed/paused process memory
without validating if we have enough information (and correct
informaton) to do so. Second, we use Error::Fail() as the primary tool
to verify if there was an error loading a field/objects/etc., but
Error::Fail usually propagates and will cause us to prematurely stop
processing the memory.

This commit introduces a few things to help us improve reliability in
the future:

  • Value classes now have a valid_ member, and Check() will take this
    member into account.
  • A new class, CheckedType, will let us wrap primitive C++ types which
    were loaded from the paused/crashed process memory, so we can have a
    Check() method for those values as well to verify if it was possible
    to load the value successfuly.
  • Two new macros, RETURN_IF_INVALID and RETURN_IF_THIS_INVALID, to
    make it easier to return from a function (with a default value) when
    a object/value was not loaded properly.

The goals in the future are:

  • Replace all uses of Error::Fail as a loading validation tool with
    RETURN_IF_INVALID, keeping Error::Fail only for unrecoverable errors
    (instead of the double semantic it has today).
  • Ensure all methods in llv8.h return either a Value subclass, or a
    primitive type wrapped in CheckedType
  • Ensure all calls to methods which will load from the target process
    memory are followed by RETURN_IF_INVALID.
  • Ensure all methods in llv8.h start with RETURN_IF_THIS_INVALID.

We could make all those changes in a single PR, but it would take a huge
amount of work and the PR would be extremely long, making it harder to
review. Instead, I suggest we make incremental changes as we see fit,
until we achieve the goals described above.

The method of choice to start was String::Representation, because by
making this method more robust we fix a crash on Node.js v12 after
running v8 bt if there's an optimized function on the stack (the
function won't be translated, but it's better than a hard crash).

@codecov-io
Copy link

codecov-io commented Sep 18, 2019

Codecov Report

Merging #294 into master will decrease coverage by 0.38%.
The diff coverage is 61.11%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #294      +/-   ##
==========================================
- Coverage   79.84%   79.45%   -0.39%     
==========================================
  Files          33       33              
  Lines        4232     4216      -16     
==========================================
- Hits         3379     3350      -29     
- Misses        853      866      +13
Impacted Files Coverage Δ
src/llv8.h 97.61% <100%> (-2.39%) ⬇️
src/llv8-inl.h 92.58% <100%> (-0.1%) ⬇️
src/llscan.cc 61.33% <27.77%> (ø) ⬆️
src/llv8.cc 73.98% <87.5%> (-1.49%) ⬇️
src/llv8-constants.h 98.48% <0%> (-1.52%) ⬇️
src/llv8-constants.cc 82.52% <0%> (-0.98%) ⬇️
src/llnode_module.cc 89.87% <0%> (-0.64%) ⬇️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0dbfad0...37b528b. Read the comment docs.

This is a first step towards making llnode more reliable. Right now,
llnode doesn't allow us to deal with partially-loaded objects (some
fields working, some not working). As a postmortem tool, llnode should
be able to handle partial loading of objects, as this is the proper way
to deal with corrupt memory. If llnode can handle corrupted memory, it
will also be able to understand the memory of unsupported Node.js
versons, although some fields and objects won't load.

There are two problems regarding reliability in llnode: first, we have
several attempts to access fields from the crashed/paused process memory
without validating if we have enough information (and correct
informaton) to do so. Second, we use Error::Fail() as the primary tool
to verify if there was an error loading a field/objects/etc., but
Error::Fail usually propagates and will cause us to prematurely stop
processing the memory.

This commit introduces a few things to help us improve reliability in
the future:

  - Value classes now have a `valid_` member, and Check() will take this
    member into account.
  - A new class, CheckedType, will let us wrap primitive C++ types which
    were loaded from the paused/crashed process memory, so we can have a
    Check() method for those values as well to verify if it was possible
    to load the value successfuly.
  - Two new macros, RETURN_IF_INVALID and RETURN_IF_THIS_INVALID, to
    make it easier to return from a function (with a default value) when
    a object/value was not loaded properly.

The goals in the future are:

  - Replace all uses of Error::Fail as a loading validation tool with
    RETURN_IF_INVALID, keeping Error::Fail only for unrecoverable errors
    (instead of the double semantic it has today).
  - Ensure all methods in llv8.h return either a Value subclass, or a
    primitive type wrapped in CheckedType
  - Ensure all calls to methods which will load from the target process
    memory are followed by RETURN_IF_INVALID.
  - Ensure all methods in llv8.h start with RETURN_IF_THIS_INVALID.

We could make all those changes in a single PR, but it would take a huge
amount of work and the PR would be extremely long, making it harder to
review. Instead, I suggest we make incremental changes as we see fit,
until we achieve the goals described above.

The method of choice to start was String::Representation, because by
making this method more robust we fix a crash on Node.js v12 after
running `v8 bt` if there's an optimized function on the stack (the
function won't be translated, but it's better than a hard crash).
mmarchini added a commit that referenced this pull request Sep 25, 2019
This is a first step towards making llnode more reliable. Right now,
llnode doesn't allow us to deal with partially-loaded objects (some
fields working, some not working). As a postmortem tool, llnode should
be able to handle partial loading of objects, as this is the proper way
to deal with corrupt memory. If llnode can handle corrupted memory, it
will also be able to understand the memory of unsupported Node.js
versons, although some fields and objects won't load.

There are two problems regarding reliability in llnode: first, we have
several attempts to access fields from the crashed/paused process memory
without validating if we have enough information (and correct
informaton) to do so. Second, we use Error::Fail() as the primary tool
to verify if there was an error loading a field/objects/etc., but
Error::Fail usually propagates and will cause us to prematurely stop
processing the memory.

This commit introduces a few things to help us improve reliability in
the future:

  - Value classes now have a `valid_` member, and Check() will take this
    member into account.
  - A new class, CheckedType, will let us wrap primitive C++ types which
    were loaded from the paused/crashed process memory, so we can have a
    Check() method for those values as well to verify if it was possible
    to load the value successfuly.
  - Two new macros, RETURN_IF_INVALID and RETURN_IF_THIS_INVALID, to
    make it easier to return from a function (with a default value) when
    a object/value was not loaded properly.

The goals in the future are:

  - Replace all uses of Error::Fail as a loading validation tool with
    RETURN_IF_INVALID, keeping Error::Fail only for unrecoverable errors
    (instead of the double semantic it has today).
  - Ensure all methods in llv8.h return either a Value subclass, or a
    primitive type wrapped in CheckedType
  - Ensure all calls to methods which will load from the target process
    memory are followed by RETURN_IF_INVALID.
  - Ensure all methods in llv8.h start with RETURN_IF_THIS_INVALID.

We could make all those changes in a single PR, but it would take a huge
amount of work and the PR would be extremely long, making it harder to
review. Instead, I suggest we make incremental changes as we see fit,
until we achieve the goals described above.

The method of choice to start was String::Representation, because by
making this method more robust we fix a crash on Node.js v12 after
running `v8 bt` if there's an optimized function on the stack (the
function won't be translated, but it's better than a hard crash).

PR-URL: #294
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
@mmarchini
Copy link
Contributor Author

Landed in a501635

@mmarchini mmarchini closed this Sep 25, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants